From dc483ee0a2f66d0be76ed2274c69d2ac902b89fa Mon Sep 17 00:00:00 2001 From: imbeacon Date: Thu, 25 May 2023 13:16:43 +0300 Subject: [PATCH 001/166] Changed method for removing relations from all to removing only COMMON relations --- .../controller/EntityRelationController.java | 2 +- .../DefaultTbEntityRelationService.java | 4 ++-- .../relation/TbEntityRelationService.java | 2 +- .../server/dao/relation/RelationService.java | 2 ++ .../dao/relation/BaseRelationService.java | 22 +++++++++++++++++-- .../dao/service/BaseRelationServiceTest.java | 18 +++++++++++++++ 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index 08448f1d28..788b48360d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -129,7 +129,7 @@ public class EntityRelationController extends BaseController { checkParameter("entityType", strType); EntityId entityId = EntityIdFactory.getByTypeAndId(strType, strId); checkEntityId(entityId, Operation.WRITE); - tbEntityRelationService.deleteRelations(getTenantId(), getCurrentUser().getCustomerId(), entityId, getCurrentUser()); + tbEntityRelationService.deleteCommonRelations(getTenantId(), getCurrentUser().getCustomerId(), entityId, getCurrentUser()); } @ApiOperation(value = "Get Relation (getRelation)", diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java index b978b730fd..cf1733490d 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java @@ -72,9 +72,9 @@ public class DefaultTbEntityRelationService extends AbstractTbEntityService impl } @Override - public void deleteRelations(TenantId tenantId, CustomerId customerId, EntityId entityId, User user) throws ThingsboardException { + public void deleteCommonRelations(TenantId tenantId, CustomerId customerId, EntityId entityId, User user) throws ThingsboardException { try { - relationService.deleteEntityRelations(tenantId, entityId); + relationService.deleteEntityCommonRelations(tenantId, entityId); notificationEntityService.logEntityAction(tenantId, entityId, null, customerId, ActionType.RELATIONS_DELETED, user); } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, entityId, null, customerId, diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java index 2caee86d0c..8bf5018c95 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java @@ -28,6 +28,6 @@ public interface TbEntityRelationService { void delete(TenantId tenantId, CustomerId customerId, EntityRelation entity, User user) throws ThingsboardException; - void deleteRelations(TenantId tenantId, CustomerId customerId, EntityId entityId, User user) throws ThingsboardException; + void deleteCommonRelations(TenantId tenantId, CustomerId customerId, EntityId entityId, User user) throws ThingsboardException; } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java index 3ddb6187ea..39e94b3a6b 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java @@ -53,6 +53,8 @@ public interface RelationService { void deleteEntityRelations(TenantId tenantId, EntityId entity); + void deleteEntityCommonRelations(TenantId tenantId, EntityId entity); + List findByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup); ListenableFuture> findByFromAsync(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup); diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index fb86e09ab3..b0d5fb3c2c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -220,13 +220,31 @@ public class BaseRelationService implements RelationService { return future; } + @Transactional + @Override + public void deleteEntityCommonRelations(TenantId tenantId, EntityId entityId) { + deleteEntityRelations(tenantId, entityId, RelationTypeGroup.COMMON); + } + @Transactional @Override public void deleteEntityRelations(TenantId tenantId, EntityId entityId) { + deleteEntityRelations(tenantId, entityId, null); + } + + @Transactional + public void deleteEntityRelations(TenantId tenantId, EntityId entityId, RelationTypeGroup relationTypeGroup) { log.trace("Executing deleteEntityRelations [{}]", entityId); validate(entityId); - List inboundRelations = new ArrayList<>(relationDao.findAllByTo(tenantId, entityId)); - List outboundRelations = new ArrayList<>(relationDao.findAllByFrom(tenantId, entityId)); + List inboundRelations; + List outboundRelations; + if (relationTypeGroup == null) { + inboundRelations = relationDao.findAllByTo(tenantId, entityId); + outboundRelations = relationDao.findAllByFrom(tenantId, entityId); + } else { + inboundRelations = relationDao.findAllByFrom(tenantId, entityId, relationTypeGroup); + outboundRelations = relationDao.findAllByTo(tenantId, entityId, relationTypeGroup); + } if (!inboundRelations.isEmpty()) { try { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java index d60a896aec..ae07930156 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java @@ -130,6 +130,24 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); } + @Test + public void testDeleteEntityCommonRelations() { + AssetId parentId = new AssetId(Uuids.timeBased()); + AssetId childId = new AssetId(Uuids.timeBased()); + AssetId subChildId = new AssetId(Uuids.timeBased()); + + EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); + EntityRelation relationB = new EntityRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE); + + saveRelation(relationA); + saveRelation(relationB); + + relationService.deleteEntityCommonRelations(SYSTEM_TENANT_ID, childId); + + Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); + Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); + } + @Test public void testFindFrom() throws ExecutionException, InterruptedException { AssetId parentA = new AssetId(Uuids.timeBased()); From c9f5654e082b0f22ffa34387892428903eadde5d Mon Sep 17 00:00:00 2001 From: imbeacon Date: Thu, 25 May 2023 16:05:25 +0300 Subject: [PATCH 002/166] Added additional methods to dao to remove only required relations --- .../dao/relation/BaseRelationService.java | 27 +++++++++++-------- .../server/dao/relation/RelationDao.java | 4 +++ .../dao/sql/relation/JpaRelationDao.java | 23 +++++++++++++--- .../dao/sql/relation/RelationRepository.java | 5 ++++ .../dao/service/BaseRelationServiceTest.java | 7 +++++ 5 files changed, 51 insertions(+), 15 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index b0d5fb3c2c..f7ae4a1596 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -236,19 +236,20 @@ public class BaseRelationService implements RelationService { public void deleteEntityRelations(TenantId tenantId, EntityId entityId, RelationTypeGroup relationTypeGroup) { log.trace("Executing deleteEntityRelations [{}]", entityId); validate(entityId); - List inboundRelations; - List outboundRelations; - if (relationTypeGroup == null) { - inboundRelations = relationDao.findAllByTo(tenantId, entityId); - outboundRelations = relationDao.findAllByFrom(tenantId, entityId); - } else { - inboundRelations = relationDao.findAllByFrom(tenantId, entityId, relationTypeGroup); - outboundRelations = relationDao.findAllByTo(tenantId, entityId, relationTypeGroup); - } + List inboundRelations = relationTypeGroup == null + ? relationDao.findAllByTo(tenantId, entityId) + : relationDao.findAllByTo(tenantId, entityId, relationTypeGroup); + List outboundRelations = relationTypeGroup == null + ? relationDao.findAllByFrom(tenantId, entityId) + : relationDao.findAllByFrom(tenantId, entityId, relationTypeGroup); if (!inboundRelations.isEmpty()) { try { - relationDao.deleteInboundRelations(tenantId, entityId); + if (relationTypeGroup == null) { + relationDao.deleteInboundRelations(tenantId, entityId); + } else { + relationDao.deleteInboundRelations(tenantId, entityId, relationTypeGroup); + } } catch (ConcurrencyFailureException e) { log.debug("Concurrency exception while deleting relations [{}]", inboundRelations, e); } @@ -259,7 +260,11 @@ public class BaseRelationService implements RelationService { } if (!outboundRelations.isEmpty()) { - relationDao.deleteOutboundRelations(tenantId, entityId); + if (relationTypeGroup == null) { + relationDao.deleteOutboundRelations(tenantId, entityId); + } else { + relationDao.deleteOutboundRelations(tenantId, entityId, relationTypeGroup); + } for (EntityRelation relation : outboundRelations) { eventPublisher.publishEvent(EntityRelationEvent.from(relation)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java index 7fee4a31ff..250a0c6105 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java @@ -64,8 +64,12 @@ public interface RelationDao { void deleteOutboundRelations(TenantId tenantId, EntityId entity); + void deleteOutboundRelations(TenantId tenantId, EntityId entity, RelationTypeGroup relationTypeGroup); + void deleteInboundRelations(TenantId tenantId, EntityId entity); + void deleteInboundRelations(TenantId tenantId, EntityId entity, RelationTypeGroup relationTypeGroup); + ListenableFuture deleteOutboundRelationsAsync(TenantId tenantId, EntityId entity); List findRuleNodeToRuleChainRelations(RuleChainType ruleChainType, int limit); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java index c1b17f160e..7e4b41702e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java @@ -34,10 +34,7 @@ import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; import org.thingsboard.server.dao.util.SqlDao; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; /** @@ -205,6 +202,15 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple } } + @Override + public void deleteOutboundRelations(TenantId tenantId, EntityId entity, RelationTypeGroup relationTypeGroup) { + try { + relationRepository.deleteByFromIdAndFromTypeAndRelationTypeGroupIn(entity.getId(), entity.getEntityType().name(), Collections.singletonList(relationTypeGroup.name())); + } catch (ConcurrencyFailureException e) { + log.debug("Concurrency exception while deleting relations [{}]", entity, e); + } + } + @Override public void deleteInboundRelations(TenantId tenantId, EntityId entity) { try { @@ -214,6 +220,15 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple } } + @Override + public void deleteInboundRelations(TenantId tenantId, EntityId entity, RelationTypeGroup relationTypeGroup) { + try { + relationRepository.deleteByToIdAndToTypeAndRelationTypeGroupIn(entity.getId(), entity.getEntityType().name(), Collections.singletonList(relationTypeGroup.name())); + } catch (ConcurrencyFailureException e) { + log.debug("Concurrency exception while deleting relations [{}]", entity, e); + } + } + @Override public ListenableFuture deleteOutboundRelationsAsync(TenantId tenantId, EntityId entity) { return service.submit( diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java index a3d6d8570d..10c8c826eb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java @@ -82,4 +82,9 @@ public interface RelationRepository @Query("DELETE FROM RelationEntity r where r.toId = :toId and r.toType = :toType and r.relationTypeGroup in :relationTypeGroups") void deleteByToIdAndToTypeAndRelationTypeGroupIn(@Param("toId") UUID toId, @Param("toType") String toType, @Param("relationTypeGroups") List relationTypeGroups); + @Transactional + @Modifying + @Query("DELETE FROM RelationEntity r where r.fromId = :fromId and r.fromType = :fromType and r.relationTypeGroup in :relationTypeGroups") + void deleteByFromIdAndFromTypeAndRelationTypeGroupIn(@Param("fromId") UUID fromId, @Param("fromType") String fromType, @Param("relationTypeGroups") List relationTypeGroups); + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java index ae07930156..afd42c5ab8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java @@ -138,14 +138,21 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); EntityRelation relationB = new EntityRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE); + EntityRelation relationC = new EntityRelation(parentId, childId, EntityRelation.MANAGES_TYPE, RelationTypeGroup.EDGE); + EntityRelation relationD = new EntityRelation(childId, subChildId, EntityRelation.MANAGES_TYPE, RelationTypeGroup.EDGE); saveRelation(relationA); saveRelation(relationB); + saveRelation(relationC); + saveRelation(relationD); relationService.deleteEntityCommonRelations(SYSTEM_TENANT_ID, childId); Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); + + Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.MANAGES_TYPE, RelationTypeGroup.EDGE)); + Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.MANAGES_TYPE, RelationTypeGroup.EDGE)); } @Test From 91aca058554615f58de5a72447b921ef565a6c91 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Thu, 25 May 2023 17:00:24 +0300 Subject: [PATCH 003/166] Imports --- .../thingsboard/server/dao/sql/relation/JpaRelationDao.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java index 7e4b41702e..31125a0791 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java @@ -34,7 +34,11 @@ import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; import org.thingsboard.server.dao.util.SqlDao; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.stream.Collectors; /** From f4404d20f0aeb6e57a6f12cfecf528e6f746f693 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Mon, 29 May 2023 16:17:33 +0300 Subject: [PATCH 004/166] Updated due to comments --- .../server/controller/EntityRelationController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index 788b48360d..e1e89734b9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -117,8 +117,8 @@ public class EntityRelationController extends BaseController { tbEntityRelationService.delete(getTenantId(), getCurrentUser().getCustomerId(), relation, getCurrentUser()); } - @ApiOperation(value = "Delete Relations (deleteRelations)", - notes = "Deletes all the relation (both 'from' and 'to' direction) for the specified entity. " + + @ApiOperation(value = "Delete common relations (deleteCommonRelations)", + notes = "Deletes all the relations ('from' and 'to' direction) for the specified entity and relation type group: 'COMMON'. " + SECURITY_CHECKS_ENTITY_DESCRIPTION) @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/relations", method = RequestMethod.DELETE, params = {"entityId", "entityType"}) From aa28b276d23210308b93959669ab71de0f0fd4dc Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 16 Jun 2023 15:40:29 +0200 Subject: [PATCH 005/166] added recalculetePartitions delay for node restart --- .../src/main/resources/thingsboard.yml | 1 + .../queue/discovery/ZkDiscoveryService.java | 31 ++++++++++++++++++- .../src/main/resources/tb-vc-executor.yml | 1 + .../src/main/resources/tb-coap-transport.yml | 1 + .../src/main/resources/tb-http-transport.yml | 1 + .../src/main/resources/tb-lwm2m-transport.yml | 1 + .../src/main/resources/tb-mqtt-transport.yml | 1 + .../src/main/resources/tb-snmp-transport.yml | 1 + 8 files changed, 37 insertions(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index e7fbbd2a3d..9cec475335 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -96,6 +96,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cluster: stats: diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index fcf80bcf3d..17d046a4cb 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -44,8 +44,10 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.List; import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -66,6 +68,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi private Integer zkSessionTimeout; @Value("${zk.zk_dir}") private String zkDir; + @Value("${zk.recalculate_delay:120000}") + private Long recalculateDelay; + + private final ConcurrentHashMap> delayedTasks; private final TbServiceInfoProvider serviceInfoProvider; private final PartitionService partitionService; @@ -82,6 +88,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi PartitionService partitionService) { this.serviceInfoProvider = serviceInfoProvider; this.partitionService = partitionService; + delayedTasks = new ConcurrentHashMap<>(); } @PostConstruct @@ -290,8 +297,30 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi log.debug("Processing [{}] event for [{}]", pathChildrenCacheEvent.getType(), instance.getServiceId()); switch (pathChildrenCacheEvent.getType()) { case CHILD_ADDED: + ScheduledFuture task = delayedTasks.remove(instance.getServiceId()); + if (task != null) { + if (!task.cancel(false)) { + log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + recalculatePartitions(); + } else { + log.debug("[{}] Recalculate partitions ignored. Service restarted in time [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + } + } else { + log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + recalculatePartitions(); + } + break; case CHILD_REMOVED: - recalculatePartitions(); + ScheduledFuture future = zkExecutorService.schedule(() -> { + log.debug("[{}] Going to recalculate partitions due to removed node [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + delayedTasks.remove(instance.getServiceId()); + recalculatePartitions(); + }, recalculateDelay, TimeUnit.MILLISECONDS); + delayedTasks.put(instance.getServiceId(), future); break; default: break; diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml index 094e0e2099..2c90082eb5 100644 --- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml +++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" queue: type: "${TB_QUEUE_TYPE:kafka}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index b9db930657..aef46a1234 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index bff7adb561..4bce6e28d7 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -68,6 +68,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index ae8f0138a7..eab5b107c8 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index 076dde0234..f0968aa6b9 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index c68c9c56a8..c7dcd70574 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" From 22874e8a65c1a353bc639be00f005a75030b9be9 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 19 Jun 2023 17:55:39 +0300 Subject: [PATCH 006/166] filter nodes && added TbMsgType enum --- .../actors/ruleChain/DefaultTbContext.java | 26 ++--- .../RuleChainActorMessageProcessor.java | 4 +- .../server/controller/RpcV2Controller.java | 4 +- .../service/action/EntityActionService.java | 94 ++---------------- .../AnnotationComponentDiscoveryService.java | 25 +++-- .../device/DeviceProvisionServiceImpl.java | 24 +++-- .../service/edge/rpc/EdgeGrpcService.java | 10 +- .../processor/device/DeviceEdgeProcessor.java | 6 +- .../telemetry/BaseTelemetryProcessor.java | 4 +- .../DefaultTbNotificationEntityService.java | 5 +- .../rpc/DefaultTbCoreDeviceRpcService.java | 5 +- .../state/DefaultDeviceStateService.java | 18 ++-- .../transport/DefaultTransportApiService.java | 3 +- .../server/common/data/DataConstants.java | 44 --------- .../server/common/data/EntityType.java | 8 ++ .../server/common/data/audit/ActionType.java | 88 +++++++++-------- .../server/common/data/msg/TbMsgType.java | 95 +++++++++++++++++++ .../engine/api/EmptyNodeConfiguration.java | 3 +- ...onTypes.java => TbNodeConnectionType.java} | 9 +- .../engine/action/TbAbstractAlarmNode.java | 15 ++- .../action/TbAbstractRelationActionNode.java | 4 +- .../TbCopyAttributesToEntityViewNode.java | 22 +++-- .../rule/engine/action/TbMsgCountNode.java | 2 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 2 +- .../deduplication/TbMsgDeduplicationNode.java | 4 +- .../rule/engine/delay/TbMsgDelayNode.java | 2 +- .../engine/edge/AbstractTbMsgPushNode.java | 52 ++++++---- .../engine/filter/TbAssetTypeSwitchNode.java | 3 +- .../engine/filter/TbCheckAlarmStatusNode.java | 37 +++----- .../filter/TbCheckAlarmStatusNodeConfig.java | 4 +- .../engine/filter/TbCheckMessageNode.java | 13 +-- .../engine/filter/TbCheckRelationNode.java | 29 +++--- .../engine/filter/TbDeviceTypeSwitchNode.java | 3 +- .../rule/engine/filter/TbJsFilterNode.java | 9 +- .../rule/engine/filter/TbJsSwitchNode.java | 3 +- .../engine/filter/TbMsgTypeFilterNode.java | 8 +- .../engine/filter/TbMsgTypeSwitchNode.java | 86 +---------------- .../filter/TbOriginatorTypeFilterNode.java | 8 +- .../filter/TbOriginatorTypeSwitchNode.java | 50 +--------- .../rule/engine/flow/TbCheckpointNode.java | 4 +- .../engine/geo/TbGpsGeofencingFilterNode.java | 8 +- .../rule/engine/kafka/TbKafkaNode.java | 4 +- .../rule/engine/mail/TbMsgToEmailNode.java | 3 +- .../TbAbstractGetEntityDetailsNode.java | 2 +- .../rule/engine/profile/DeviceState.java | 31 ++++-- .../engine/profile/TbDeviceProfileNode.java | 8 +- .../rule/engine/rest/TbHttpClient.java | 4 +- .../rule/engine/rpc/TbSendRPCRequestNode.java | 7 +- .../transform/TbAbstractTransformNode.java | 4 +- .../engine/transform/TbSplitArrayMsgNode.java | 4 +- .../action/TbCreateRelationNodeTest.java | 13 +-- .../engine/edge/TbMsgPushToEdgeNodeTest.java | 18 ++-- .../engine/filter/TbJsFilterNodeTest.java | 14 +-- .../rule/engine/profile/DeviceStateTest.java | 15 +-- .../transform/TbMsgDeduplicationNodeTest.java | 12 +-- 55 files changed, 464 insertions(+), 518 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java rename rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/{TbRelationTypes.java => TbNodeConnectionType.java} (74%) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 8b76924d96..778a3db51f 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.actors.ruleChain; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.netty.channel.EventLoopGroup; @@ -34,7 +33,7 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.util.TenantIdLoader; @@ -42,7 +41,6 @@ import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; @@ -114,6 +112,10 @@ import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; + /** * Created by ashvayka on 19.03.18. */ @@ -132,7 +134,7 @@ class DefaultTbContext implements TbContext { @Override public void tellSuccess(TbMsg msg) { - tellNext(msg, Collections.singleton(TbRelationTypes.SUCCESS), null); + tellNext(msg, Collections.singleton(TbNodeConnectionType.SUCCESS), null); } @Override @@ -211,7 +213,7 @@ class DefaultTbContext implements TbContext { @Override public void enqueueForTellFailure(TbMsg tbMsg, String failureMessage) { TopicPartitionInfo tpi = resolvePartition(tbMsg); - enqueueForTellNext(tpi, tbMsg, Collections.singleton(TbRelationTypes.FAILURE), failureMessage, null, null); + enqueueForTellNext(tpi, tbMsg, Collections.singleton(TbNodeConnectionType.FAILURE), failureMessage, null, null); } @Override @@ -309,7 +311,7 @@ class DefaultTbContext implements TbContext { @Override public void tellFailure(TbMsg msg, Throwable th) { if (nodeCtx.getSelf().isDebugMode()) { - mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, TbRelationTypes.FAILURE, th); + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, TbNodeConnectionType.FAILURE, th); } String failureMessage; if (th != null) { @@ -322,7 +324,7 @@ class DefaultTbContext implements TbContext { failureMessage = null; } nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getRuleChainId(), - nodeCtx.getSelf().getId(), Collections.singleton(TbRelationTypes.FAILURE), + nodeCtx.getSelf().getId(), Collections.singleton(TbNodeConnectionType.FAILURE), msg, failureMessage)); } @@ -346,7 +348,7 @@ class DefaultTbContext implements TbContext { } public TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId) { - return entityActionMsg(customer, customer.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); + return entityActionMsg(customer, customer.getId(), ruleNodeId, ENTITY_CREATED.name()); } public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) { @@ -354,7 +356,7 @@ class DefaultTbContext implements TbContext { if (device.getDeviceProfileId() != null) { deviceProfile = mainCtx.getDeviceProfileCache().find(device.getDeviceProfileId()); } - return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED, deviceProfile); + return entityActionMsg(device, device.getId(), ruleNodeId, ENTITY_CREATED.name(), deviceProfile); } public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) { @@ -362,7 +364,7 @@ class DefaultTbContext implements TbContext { if (asset.getAssetProfileId() != null) { assetProfile = mainCtx.getAssetProfileCache().find(asset.getAssetProfileId()); } - return entityActionMsg(asset, asset.getId(), ruleNodeId, DataConstants.ENTITY_CREATED, assetProfile); + return entityActionMsg(asset, asset.getId(), ruleNodeId, ENTITY_CREATED.name(), assetProfile); } public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) { @@ -382,7 +384,7 @@ class DefaultTbContext implements TbContext { if (attributes != null) { attributes.forEach(attributeKvEntry -> JacksonUtil.addKvEntry(entityNode, attributeKvEntry)); } - return attributesActionMsg(originator, ruleNodeId, scope, DataConstants.ATTRIBUTES_UPDATED, JacksonUtil.toString(entityNode)); + return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_UPDATED.name(), JacksonUtil.toString(entityNode)); } public TbMsg attributesDeletedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List keys) { @@ -391,7 +393,7 @@ class DefaultTbContext implements TbContext { if (keys != null) { keys.forEach(attrsArrayNode::add); } - return attributesActionMsg(originator, ruleNodeId, scope, DataConstants.ATTRIBUTES_DELETED, JacksonUtil.toString(entityNode)); + return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_DELETED.name(), JacksonUtil.toString(entityNode)); } private TbMsg attributesActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, String action, String msgData) { diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index 4bd082ee38..351a012d73 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -16,7 +16,7 @@ package org.thingsboard.server.actors.ruleChain; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorRef; @@ -307,7 +307,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor msgType = actionType.getRuleEngineMsgType(); + if (msgType.isPresent()) { try { TbMsgMetaData metaData = new TbMsgMetaData(); if (user != null) { @@ -247,7 +171,7 @@ public class EntityActionService { if (tenantId != null && !tenantId.isSysTenantId()) { processNotificationRules(tenantId, entityId, entity, actionType, user, additionalInfo); } - TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); + TbMsg tbMsg = TbMsg.newMsg(msgType.get().name(), entityId, customerId, metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null); } catch (Exception e) { log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e); diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index ad65ee805b..e8fff7ba60 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -30,8 +30,12 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeDefinition; import org.thingsboard.rule.engine.api.RuleNode; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbVersionedNode; +import org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode; +import org.thingsboard.rule.engine.filter.TbOriginatorTypeSwitchNode; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -194,7 +198,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe scannedComponent.setName(ruleNodeAnnotation.name()); scannedComponent.setScope(ruleNodeAnnotation.scope()); scannedComponent.setClusteringMode(ruleNodeAnnotation.clusteringMode()); - NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation); + NodeDefinition nodeDefinition = prepareNodeDefinition(clazz, ruleNodeAnnotation); ObjectNode configurationDescriptor = JacksonUtil.newObjectNode(); JsonNode node = JacksonUtil.valueToTree(nodeDefinition); configurationDescriptor.set("nodeDefinition", node); @@ -221,13 +225,13 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe return scannedComponent; } - private NodeDefinition prepareNodeDefinition(RuleNode nodeAnnotation) throws Exception { + private NodeDefinition prepareNodeDefinition(Class clazz, RuleNode nodeAnnotation) throws Exception { NodeDefinition nodeDefinition = new NodeDefinition(); nodeDefinition.setDetails(nodeAnnotation.nodeDetails()); nodeDefinition.setDescription(nodeAnnotation.nodeDescription()); nodeDefinition.setInEnabled(nodeAnnotation.inEnabled()); nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled()); - nodeDefinition.setRelationTypes(getRelationTypesWithFailureRelation(nodeAnnotation)); + nodeDefinition.setRelationTypes(getRelationTypesWithFailureRelation(clazz, nodeAnnotation)); nodeDefinition.setCustomRelations(nodeAnnotation.customRelations()); nodeDefinition.setRuleChainNode(nodeAnnotation.ruleChainNode()); Class configClazz = nodeAnnotation.configClazz(); @@ -242,10 +246,17 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe return nodeDefinition; } - private String[] getRelationTypesWithFailureRelation(RuleNode nodeAnnotation) { + private String[] getRelationTypesWithFailureRelation(Class clazz, RuleNode nodeAnnotation) { List relationTypes = new ArrayList<>(Arrays.asList(nodeAnnotation.relationTypes())); - if (!relationTypes.contains(TbRelationTypes.FAILURE)) { - relationTypes.add(TbRelationTypes.FAILURE); + if (TbOriginatorTypeSwitchNode.class.equals(clazz)) { + relationTypes.addAll(EntityType.NORMAL_NAMES); + } + if (TbMsgTypeSwitchNode.class.equals(clazz)) { + relationTypes.addAll(TbMsgType.NODE_CONNECTIONS); + relationTypes.add(TbMsgType.OTHER); + } + if (!relationTypes.contains(TbNodeConnectionType.FAILURE)) { + relationTypes.add(TbNodeConnectionType.FAILURE); } return relationTypes.toArray(new String[relationTypes.size()]); } diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index beecebe2ba..3bed8a1905 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -23,7 +23,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; @@ -69,6 +68,11 @@ import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_FAILURE; +import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_SUCCESS; + @Service @Slf4j @@ -162,7 +166,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) { if (targetDevice != null) { log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName()); - notify(targetDevice, provisionRequest, DataConstants.PROVISION_FAILURE, false); + notify(targetDevice, provisionRequest, PROVISION_FAILURE.name(), false); throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } else { return createDevice(provisionRequest, targetProfile); @@ -188,13 +192,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private ProvisionResponse processProvision(Device device, ProvisionRequest provisionRequest) { try { Optional provisionState = attributesService.find(device.getTenantId(), device.getId(), - DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE).get(); + SERVER_SCOPE, DEVICE_PROVISION_STATE).get(); if (provisionState != null && provisionState.isPresent() && !provisionState.get().getValueAsString().equals(PROVISIONED_STATE)) { - notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + notify(device, provisionRequest, PROVISION_FAILURE.name(), false); throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } else { saveProvisionStateAttribute(device).get(); - notify(device, provisionRequest, DataConstants.PROVISION_SUCCESS, true); + notify(device, provisionRequest, PROVISION_SUCCESS.name(), true); } } catch (InterruptedException | ExecutionException e) { throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); @@ -222,14 +226,14 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { clusterService.onDeviceUpdated(savedDevice, null); saveProvisionStateAttribute(savedDevice).get(); pushDeviceCreatedEventToRuleEngine(savedDevice); - notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); + notify(savedDevice, provisionRequest, PROVISION_SUCCESS.name(), true); return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS); } catch (Exception e) { log.warn("[{}] Error during device creation from provision request: [{}]", provisionRequest.getDeviceName(), provisionRequest, e); Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); if (device != null) { - notify(device, provisionRequest, DataConstants.PROVISION_FAILURE, false); + notify(device, provisionRequest, PROVISION_FAILURE.name(), false); } throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } @@ -244,7 +248,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } private ListenableFuture> saveProvisionStateAttribute(Device device) { - return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, + return attributesService.save(device.getTenantId(), device.getId(), SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE), System.currentTimeMillis()))); } @@ -266,10 +270,10 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private void pushDeviceCreatedEventToRuleEngine(Device device) { try { ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); - TbMsg msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); + TbMsg msg = TbMsg.newMsg(ENTITY_CREATED.name(), device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); sendToRuleEngine(device.getTenantId(), msg, null); } catch (JsonProcessingException | IllegalArgumentException e) { - log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), DataConstants.ENTITY_CREATED, e); + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), ENTITY_CREATED, e); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index b7334ebf5a..233dcba2cc 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -71,9 +71,9 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; -import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT; -import static org.thingsboard.server.common.data.DataConstants.DISCONNECT_EVENT; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; @Service @Slf4j @@ -278,7 +278,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, true); long lastConnectTs = System.currentTimeMillis(); save(edgeId, DefaultDeviceStateService.LAST_CONNECT_TIME, lastConnectTs); - pushRuleEngineMessage(edgeGrpcSession.getEdge().getTenantId(), edgeId, lastConnectTs, CONNECT_EVENT); + pushRuleEngineMessage(edgeGrpcSession.getEdge().getTenantId(), edgeId, lastConnectTs, CONNECT_EVENT.name()); cancelScheduleEdgeEventsCheck(edgeId); scheduleEdgeEventsCheck(edgeGrpcSession); } @@ -395,7 +395,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, false); long lastDisconnectTs = System.currentTimeMillis(); save(edgeId, DefaultDeviceStateService.LAST_DISCONNECT_TIME, lastDisconnectTs); - pushRuleEngineMessage(toRemove.getEdge().getTenantId(), edgeId, lastDisconnectTs, DISCONNECT_EVENT); + pushRuleEngineMessage(toRemove.getEdge().getTenantId(), edgeId, lastDisconnectTs, DISCONNECT_EVENT.name()); cancelScheduleEdgeEventsCheck(edgeId); } else { log.debug("[{}] edge session [{}] is not available anymore, nothing to remove. most probably this session is already outdated!", edgeId, sessionId); @@ -451,7 +451,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i private void pushRuleEngineMessage(TenantId tenantId, EdgeId edgeId, long ts, String msgType) { try { ObjectNode edgeState = JacksonUtil.newObjectNode(); - if (msgType.equals(CONNECT_EVENT)) { + if (msgType.equals(CONNECT_EVENT.name())) { edgeState.put(DefaultDeviceStateService.ACTIVITY_STATE, true); edgeState.put(DefaultDeviceStateService.LAST_CONNECT_TIME, ts); } else { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java index 3e617de8c6..0d48f1532f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java @@ -62,6 +62,8 @@ import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; import java.util.UUID; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; + @Component @Slf4j @TbCoreComponent @@ -124,7 +126,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { try { Device device = deviceService.findDeviceById(tenantId, deviceId); ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); - TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, device.getCustomerId(), + TbMsg tbMsg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, device.getCustomerId(), getActionTbMsgMetaData(edge, device.getCustomerId()), TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { @Override @@ -138,7 +140,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { } }); } catch (JsonProcessingException | IllegalArgumentException e) { - log.warn("[{}] Failed to push device action to rule engine: {}", deviceId, DataConstants.ENTITY_CREATED, e); + log.warn("[{}] Failed to push device action to rule engine: {}", deviceId, ENTITY_CREATED.name(), e); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index fece86b92a..6a7ffe2c40 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -73,6 +73,8 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; + @Slf4j public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { @@ -257,7 +259,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { @Override public void onSuccess(@Nullable Void tmp) { var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), DataConstants.ATTRIBUTES_UPDATED, entityId, + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), ATTRIBUTES_UPDATED.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java index 0a75404fb4..719158403c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java @@ -21,7 +21,6 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; @@ -53,6 +52,8 @@ import org.thingsboard.server.service.gateway_device.GatewayNotificationsService import java.util.List; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED_FROM_TENANT; + @Slf4j @Service @RequiredArgsConstructor @@ -286,7 +287,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) { String data = JacksonUtil.toString(JacksonUtil.valueToTree(assignedDevice)); if (data != null) { - TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), + TbMsg tbMsg = TbMsg.newMsg(ENTITY_ASSIGNED_FROM_TENANT.name(), assignedDevice.getId(), assignedDevice.getCustomerId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null); } diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java index bca7c7d81a..2ec8f09bd0 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.rpc; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -49,6 +48,8 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import static org.thingsboard.server.common.data.msg.TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE; + /** * Created by ashvayka on 27.03.18. */ @@ -182,7 +183,7 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { entityNode.put(DataConstants.ADDITIONAL_INFO, msg.getAdditionalInfo()); try { - TbMsg tbMsg = TbMsg.newMsg(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), Optional.ofNullable(currentUser).map(User::getCustomerId).orElse(null), metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); + TbMsg tbMsg = TbMsg.newMsg(RPC_CALL_FROM_SERVER_TO_DEVICE.name(), msg.getDeviceId(), Optional.ofNullable(currentUser).map(User::getCustomerId).orElse(null), metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); clusterService.pushMsgToRuleEngine(msg.getTenantId(), msg.getDeviceId(), tbMsg, null); } catch (IllegalArgumentException e) { throw new RuntimeException(e); diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 554359fd6d..965b781fe9 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -51,6 +51,7 @@ import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityTrigger; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.query.EntityData; @@ -63,7 +64,6 @@ 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.NotificationRuleProcessor; -import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityTrigger; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; @@ -102,11 +102,11 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import static org.thingsboard.server.common.data.DataConstants.ACTIVITY_EVENT; -import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT; -import static org.thingsboard.server.common.data.DataConstants.DISCONNECT_EVENT; -import static org.thingsboard.server.common.data.DataConstants.INACTIVITY_EVENT; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; /** * Created by ashvayka on 01.05.18. @@ -229,7 +229,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService NORMAL_NAMES = EnumSet.allOf(EntityType.class).stream() + .map(EntityType::getNormalName).collect(Collectors.toUnmodifiableList()); + @Getter private final String normalName = StringUtils.capitalize(StringUtils.removeStart(name(), "TB_") .toLowerCase().replaceAll("_", " ")); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java index 01009751d4..056be5a958 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java @@ -16,49 +16,61 @@ package org.thingsboard.server.common.data.audit; import lombok.Getter; +import org.thingsboard.server.common.data.msg.TbMsgType; + +import java.util.Optional; -@Getter public enum ActionType { - ADDED(false), // log entity - DELETED(false), // log string id - UPDATED(false), // log entity - ATTRIBUTES_UPDATED(false), // log attributes/values - ATTRIBUTES_DELETED(false), // log attributes - TIMESERIES_UPDATED(false), // log timeseries update - TIMESERIES_DELETED(false), // log timeseries - RPC_CALL(false), // log method and params - CREDENTIALS_UPDATED(false), // log new credentials - ASSIGNED_TO_CUSTOMER(false), // log customer name - UNASSIGNED_FROM_CUSTOMER(false), // log customer name - ACTIVATED(false), // log string id - SUSPENDED(false), // log string id - CREDENTIALS_READ(true), // log device id - ATTRIBUTES_READ(true), // log attributes - RELATION_ADD_OR_UPDATE(false), - RELATION_DELETED(false), - RELATIONS_DELETED(false), - ALARM_ACK(false), - ALARM_CLEAR(false), - ALARM_DELETE(false), - ALARM_ASSIGNED(false), - ALARM_UNASSIGNED(false), - LOGIN(false), - LOGOUT(false), - LOCKOUT(false), - ASSIGNED_FROM_TENANT(false), - ASSIGNED_TO_TENANT(false), - PROVISION_SUCCESS(false), - PROVISION_FAILURE(false), - ASSIGNED_TO_EDGE(false), // log edge name - UNASSIGNED_FROM_EDGE(false), - ADDED_COMMENT(false), - UPDATED_COMMENT(false), - DELETED_COMMENT(false), - SMS_SENT(false); + ADDED(false, TbMsgType.ENTITY_CREATED), // log entity + DELETED(false, TbMsgType.ENTITY_DELETED), // log string id + UPDATED(false, TbMsgType.ENTITY_UPDATED), // log entity + ATTRIBUTES_UPDATED(false, TbMsgType.ATTRIBUTES_UPDATED), // log attributes/values + ATTRIBUTES_DELETED(false, TbMsgType.ATTRIBUTES_DELETED), // log attributes + TIMESERIES_UPDATED(false, TbMsgType.TIMESERIES_UPDATED), // log timeseries update + TIMESERIES_DELETED(false, TbMsgType.TIMESERIES_DELETED), // log timeseries + RPC_CALL(false, null), // log method and params + CREDENTIALS_UPDATED(false, null), // log new credentials + ASSIGNED_TO_CUSTOMER(false, TbMsgType.ENTITY_ASSIGNED), // log customer name + UNASSIGNED_FROM_CUSTOMER(false, TbMsgType.ENTITY_UNASSIGNED), // log customer name + ACTIVATED(false, null), // log string id + SUSPENDED(false, null), // log string id + CREDENTIALS_READ(true, null), // log device id + ATTRIBUTES_READ(true, null), // log attributes + RELATION_ADD_OR_UPDATE(false, TbMsgType.RELATION_ADD_OR_UPDATE), + RELATION_DELETED(false, TbMsgType.RELATION_DELETED), + RELATIONS_DELETED(false, TbMsgType.RELATIONS_DELETED), + ALARM_ACK(false, TbMsgType.ALARM_ACK), + ALARM_CLEAR(false, TbMsgType.ALARM_CLEAR), + ALARM_DELETE(false, TbMsgType.ALARM_DELETE), + ALARM_ASSIGNED(false, TbMsgType.ALARM_ASSIGNED), + ALARM_UNASSIGNED(false, TbMsgType.ALARM_UNASSIGNED), + LOGIN(false, null), + LOGOUT(false, null), + LOCKOUT(false, null), + ASSIGNED_FROM_TENANT(false, TbMsgType.ENTITY_ASSIGNED_FROM_TENANT), + ASSIGNED_TO_TENANT(false, TbMsgType.ENTITY_ASSIGNED_TO_TENANT), + PROVISION_SUCCESS(false, TbMsgType.PROVISION_SUCCESS), + PROVISION_FAILURE(false, TbMsgType.PROVISION_FAILURE), + ASSIGNED_TO_EDGE(false, TbMsgType.ENTITY_ASSIGNED_TO_EDGE), // log edge name + UNASSIGNED_FROM_EDGE(false, TbMsgType.ENTITY_UNASSIGNED_FROM_EDGE), + ADDED_COMMENT(false, TbMsgType.COMMENT_CREATED), + UPDATED_COMMENT(false, TbMsgType.COMMENT_UPDATED), + DELETED_COMMENT(false, null), + SMS_SENT(false, null); + + @Getter private final boolean isRead; - ActionType(boolean isRead) { + private final TbMsgType ruleEngineMsgType; + + ActionType(boolean isRead, TbMsgType ruleEngineMsgType) { this.isRead = isRead; + this.ruleEngineMsgType = ruleEngineMsgType; } + + public Optional getRuleEngineMsgType() { + return Optional.ofNullable(ruleEngineMsgType); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java new file mode 100644 index 0000000000..645b8cc9fd --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java @@ -0,0 +1,95 @@ +/** + * 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.msg; + +import lombok.Getter; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public enum TbMsgType { + + POST_ATTRIBUTES_REQUEST("Post attributes"), + POST_TELEMETRY_REQUEST("Post telemetry"), + TO_SERVER_RPC_REQUEST("RPC Request from Device"), + ACTIVITY_EVENT("Activity Event"), + INACTIVITY_EVENT("Inactivity Event"), + CONNECT_EVENT("Connect Event"), + DISCONNECT_EVENT("Disconnect Event"), + ENTITY_CREATED("Entity Created"), + ENTITY_UPDATED("Entity Updated"), + ENTITY_DELETED("Entity Deleted"), + ENTITY_ASSIGNED("Entity Assigned"), + ENTITY_UNASSIGNED("Entity Unassigned"), + ATTRIBUTES_UPDATED("Attributes Updated"), + ATTRIBUTES_DELETED("Attributes Deleted"), + ALARM(null), + ALARM_ACK("Alarm Acknowledged"), + ALARM_CLEAR("Alarm Cleared"), + ALARM_DELETE("Alarm Deleted"), + ALARM_ASSIGNED("Alarm Assigned"), + ALARM_UNASSIGNED("Alarm Unassigned"), + COMMENT_CREATED("Comment Created"), + COMMENT_UPDATED("Comment Updated"), + RPC_CALL_FROM_SERVER_TO_DEVICE("RPC Request to Device"), + ENTITY_ASSIGNED_FROM_TENANT("Entity Assigned From Tenant"), + ENTITY_ASSIGNED_TO_TENANT("Entity Assigned To Tenant"), + ENTITY_ASSIGNED_TO_EDGE(null), + ENTITY_UNASSIGNED_FROM_EDGE(null), + TIMESERIES_UPDATED("Timeseries Updated"), + TIMESERIES_DELETED("Timeseries Deleted"), + RPC_QUEUED("RPC Queued"), + RPC_SENT("RPC Sent"), + RPC_DELIVERED("RPC Delivered"), + RPC_SUCCESSFUL("RPC Successful"), + RPC_TIMEOUT("RPC Timeout"), + RPC_EXPIRED("RPC Expired"), + RPC_FAILED("RPC Failed"), + RPC_DELETED("RPC Deleted"), + RELATION_ADD_OR_UPDATE("Relation Added or Updated"), + RELATION_DELETED("Relation Deleted"), + RELATIONS_DELETED("All Relations Deleted"), + PROVISION_SUCCESS(null), + PROVISION_FAILURE(null); + + public static final String OTHER = "Other"; + + public static final List NODE_CONNECTIONS = EnumSet.allOf(TbMsgType.class).stream() + .map(TbMsgType::getNodeConnection).filter(Objects::nonNull).collect(Collectors.toUnmodifiableList()); + + @Getter + private final String nodeConnection; + + TbMsgType(String nodeConnection) { + this.nodeConnection = nodeConnection; + } + + public static String getNodeConnection(String msgType) { + if (msgType == null) { + return OTHER; + } else { + return Arrays.stream(TbMsgType.values()) + .filter(type -> type.name().equals(msgType)) + .findFirst() + .map(TbMsgType::getNodeConnection) + .orElse(OTHER); + } + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java index 5c54687205..22ffe34769 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java @@ -24,7 +24,6 @@ public class EmptyNodeConfiguration implements NodeConfiguration { if (alarmResult.alarm == null) { - ctx.tellNext(msg, "False"); + ctx.tellNext(msg, TbNodeConnectionType.FALSE); } else if (alarmResult.isCreated) { - tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_CREATED, "Created"); + tellNext(ctx, msg, alarmResult, ENTITY_CREATED.name(), "Created"); } else if (alarmResult.isUpdated) { - tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_UPDATED, "Updated"); + tellNext(ctx, msg, alarmResult, ENTITY_UPDATED.name(), "Updated"); } else if (alarmResult.isCleared) { - tellNext(ctx, msg, alarmResult, DataConstants.ALARM_CLEAR, "Cleared"); + tellNext(ctx, msg, alarmResult, ALARM_CLEAR.name(), "Cleared"); } else { ctx.tellSuccess(msg); } @@ -96,7 +101,7 @@ public abstract class TbAbstractAlarmNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 6f7bb7cf8d..61004bfa11 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -36,7 +36,6 @@ import org.thingsboard.server.common.data.objects.AttributesEntityView; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.util.CollectionsUtil; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import javax.annotation.Nullable; @@ -45,7 +44,12 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; +import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( @@ -71,14 +75,14 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (DataConstants.ATTRIBUTES_UPDATED.equals(msg.getType()) || - DataConstants.ATTRIBUTES_DELETED.equals(msg.getType()) || - DataConstants.ACTIVITY_EVENT.equals(msg.getType()) || - DataConstants.INACTIVITY_EVENT.equals(msg.getType()) || - SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msg.getType())) { + if (ATTRIBUTES_UPDATED.name().equals(msg.getType()) || + ATTRIBUTES_DELETED.name().equals(msg.getType()) || + ACTIVITY_EVENT.name().equals(msg.getType()) || + INACTIVITY_EVENT.name().equals(msg.getType()) || + POST_ATTRIBUTES_REQUEST.name().equals(msg.getType())) { if (!msg.getMetaData().getData().isEmpty()) { long now = System.currentTimeMillis(); - String scope = msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) ? + String scope = msg.getType().equals(POST_ATTRIBUTES_REQUEST.name()) ? DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue(DataConstants.SCOPE); ListenableFuture> entityViewsFuture = @@ -90,7 +94,7 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { long startTime = entityView.getStartTimeMs(); long endTime = entityView.getEndTimeMs(); if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) { - if (DataConstants.ATTRIBUTES_DELETED.equals(msg.getType())) { + if (ATTRIBUTES_DELETED.name().equals(msg.getType())) { List attributes = new ArrayList<>(); for (JsonElement element : new JsonParser().parse(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray()) { if (element.isJsonPrimitive()) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java index 88f2be46a8..58c42a1109 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java @@ -33,7 +33,7 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; +import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 51effe02ef..6bd8f52285 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -41,7 +41,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static org.thingsboard.common.util.DonAsynchron.withCallback; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; +import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index 6890aa6bf4..af119d263f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -24,7 +24,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -196,7 +196,7 @@ public class TbMsgDeduplicationNode implements TbNode { private void enqueueForTellNextWithRetry(TbContext ctx, TbMsg msg, int retryAttempt) { if (config.getMaxRetries() > retryAttempt) { - ctx.enqueueForTellNext(msg, TbRelationTypes.SUCCESS, + ctx.enqueueForTellNext(msg, TbNodeConnectionType.SUCCESS, () -> { log.trace("[{}][{}][{}] Successfully enqueue deduplication result message!", ctx.getSelfId(), msg.getOriginator(), retryAttempt); }, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index 1f18aea86e..17224c18b8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -32,7 +32,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; +import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java index 318b38aa39..bbec000077 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java @@ -30,13 +30,23 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; +import static org.thingsboard.server.common.data.msg.TbMsgType.TIMESERIES_UPDATED; + @Slf4j public abstract class AbstractTbMsgPushNode implements TbNode { @@ -73,7 +83,7 @@ public abstract class AbstractTbMsgPushNode metadata) { EdgeEventActionType actionType; - if (SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType) - || DataConstants.TIMESERIES_UPDATED.equals(msgType)) { + if (POST_TELEMETRY_REQUEST.name().equals(msgType) + || TIMESERIES_UPDATED.name().equals(msgType)) { actionType = EdgeEventActionType.TIMESERIES_UPDATED; - } else if (DataConstants.ATTRIBUTES_UPDATED.equals(msgType)) { + } else if (ATTRIBUTES_UPDATED.name().equals(msgType)) { actionType = EdgeEventActionType.ATTRIBUTES_UPDATED; - } else if (SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msgType)) { + } else if (POST_ATTRIBUTES_REQUEST.name().equals(msgType)) { actionType = EdgeEventActionType.POST_ATTRIBUTES; - } else if (DataConstants.ATTRIBUTES_DELETED.equals(msgType)) { + } else if (ATTRIBUTES_DELETED.name().equals(msgType)) { actionType = EdgeEventActionType.ATTRIBUTES_DELETED; - } else if (DataConstants.CONNECT_EVENT.equals(msgType) - || DataConstants.DISCONNECT_EVENT.equals(msgType) - || DataConstants.ACTIVITY_EVENT.equals(msgType) - || DataConstants.INACTIVITY_EVENT.equals(msgType)) { + } else if (CONNECT_EVENT.name().equals(msgType) + || DISCONNECT_EVENT.name().equals(msgType) + || ACTIVITY_EVENT.name().equals(msgType) + || INACTIVITY_EVENT.name().equals(msgType)) { String scope = metadata.get(SCOPE); if ( StringUtils.isEmpty(scope)) { actionType = EdgeEventActionType.TIMESERIES_UPDATED; @@ -177,16 +187,16 @@ public abstract class AbstractTbMsgPushNodeFailure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbAssetTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java index dcaf6698cd..f76366e622 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java @@ -18,7 +18,6 @@ package org.thingsboard.rule.engine.filter; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; @@ -26,26 +25,27 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import javax.annotation.Nullable; -import java.io.IOException; @Slf4j @RuleNode( type = ComponentType.FILTER, - name = "check alarm status", + name = "alarm status filter", configClazz = TbCheckAlarmStatusNodeConfig.class, - relationTypes = {"True", "False"}, + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Checks alarm status.", - nodeDetails = "Checks the alarm status to match one of the specified statuses.", + nodeDetails = "Checks the alarm status to match one of the specified statuses.

" + + "Output connection types: True, False, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckAlarmStatusConfig") public class TbCheckAlarmStatusNode implements TbNode { + private TbCheckAlarmStatusNodeConfig config; @Override @@ -60,33 +60,24 @@ public class TbCheckAlarmStatusNode implements TbNode { ListenableFuture latest = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), alarm.getId()); - Futures.addCallback(latest, new FutureCallback() { + Futures.addCallback(latest, new FutureCallback<>() { @Override public void onSuccess(@Nullable Alarm result) { - if (result != null) { - boolean isPresent = false; - for (AlarmStatus alarmStatus : config.getAlarmStatusList()) { - if (result.getStatus() == alarmStatus) { - isPresent = true; - break; - } - } - if (isPresent) { - ctx.tellNext(msg, "True"); - } else { - ctx.tellNext(msg, "False"); - } - } else { + if (result == null) { ctx.tellFailure(msg, new TbNodeException("No such alarm found.")); + return; } + boolean isPresent = config.getAlarmStatusList().stream() + .anyMatch(alarmStatus -> result.getStatus() == alarmStatus); + ctx.tellNext(msg, isPresent ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); } @Override public void onFailure(Throwable t) { ctx.tellFailure(msg, t); } - }, MoreExecutors.directExecutor()); - } catch (IllegalArgumentException e) { + }, ctx.getDbCallbackExecutor()); + } catch (Exception e) { log.error("Failed to parse alarm: [{}]", msg.getData()); throw new TbNodeException(e); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java index d979eb10ca..4c15d635b8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java @@ -24,12 +24,14 @@ import java.util.List; @Data public class TbCheckAlarmStatusNodeConfig implements NodeConfiguration { + private List alarmStatusList; @Override public TbCheckAlarmStatusNodeConfig defaultConfiguration() { - TbCheckAlarmStatusNodeConfig config = new TbCheckAlarmStatusNodeConfig(); + var config = new TbCheckAlarmStatusNodeConfig(); config.setAlarmStatusList(Arrays.asList(AlarmStatus.ACTIVE_ACK, AlarmStatus.ACTIVE_UNACK)); return config; } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java index 3853b3a62a..c461b258a8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java @@ -22,6 +22,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -33,12 +34,12 @@ import java.util.Map; @RuleNode( type = ComponentType.FILTER, name = "check fields presence", - relationTypes = {"True", "False"}, + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, configClazz = TbCheckMessageNodeConfiguration.class, nodeDescription = "Checks the presence of the specified fields in the message and/or metadata.", - nodeDetails = "Checks the presence of the specified fields in the message and/or metadata. " + - "By default, the rule node checks that all specified fields need to be present. " + - "Uncheck the 'Check that all specified fields are present' if the presence of at least one field is sufficient.", + nodeDetails = "By default, the rule node checks that all specified fields are present. " + + "Uncheck the 'Check that all selected fields are present' if the presence of at least one field is sufficient.

" + + "Output connection types: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckMessageConfig") public class TbCheckMessageNode implements TbNode { @@ -60,9 +61,9 @@ public class TbCheckMessageNode implements TbNode { public void onMsg(TbContext ctx, TbMsg msg) { try { if (config.isCheckAllKeys()) { - ctx.tellNext(msg, allKeysData(msg) && allKeysMetadata(msg) ? "True" : "False"); + ctx.tellNext(msg, allKeysData(msg) && allKeysMetadata(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); } else { - ctx.tellNext(msg, atLeastOneData(msg) || atLeastOneMetadata(msg) ? "True" : "False"); + ctx.tellNext(msg, atLeastOneData(msg) || atLeastOneMetadata(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); } } catch (Exception e) { ctx.tellFailure(msg, e); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index 4a0d6b05e7..43bec5a3c1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -43,12 +44,14 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; @Slf4j @RuleNode( type = ComponentType.FILTER, - name = "check relation", + name = "check relation presence", configClazz = TbCheckRelationNodeConfiguration.class, - relationTypes = {"True", "False"}, + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Checks the presence of the relation between the originator of the message and other entities.", - nodeDetails = "If 'check relation to specific entity' is selected, one must specify a related entity. " + - "Otherwise, the rule node checks the presence of a relation to any entity that matches the direction and relation type criteria.", + nodeDetails = "If 'check relation to specific entity' is selected, you should specify a related entity. " + + "Otherwise, the rule node checks the presence of a relation to any entity. " + + "In both cases, relation lookup is based on configured direction and type.

" + + "Output connection types: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckRelationConfig") public class TbCheckRelationNode implements TbNode { @@ -67,13 +70,11 @@ public class TbCheckRelationNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { - ListenableFuture checkRelationFuture; - if (config.isCheckForSingleEntity()) { - checkRelationFuture = processSingle(ctx, msg); - } else { - checkRelationFuture = processList(ctx, msg); - } - withCallback(checkRelationFuture, filterResult -> ctx.tellNext(msg, filterResult ? "True" : "False"), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); + ListenableFuture checkRelationFuture = config.isCheckForSingleEntity() ? + processSingle(ctx, msg) : processList(ctx, msg); + withCallback(checkRelationFuture, + filterResult -> ctx.tellNext(msg, filterResult ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE), + t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); } private ListenableFuture processSingle(TbContext ctx, TbMsg msg) { @@ -100,11 +101,7 @@ public class TbCheckRelationNode implements TbNode { } private ListenableFuture isEmptyList(List entityRelations) { - if (entityRelations.isEmpty()) { - return Futures.immediateFuture(false); - } else { - return Futures.immediateFuture(true); - } + return entityRelations.isEmpty() ? Futures.immediateFuture(false) : Futures.immediateFuture(true); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java index 16131ecc05..4409702d9a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java @@ -34,7 +34,8 @@ import org.thingsboard.server.common.data.plugin.ComponentType; relationTypes = {"default"}, configClazz = EmptyNodeConfiguration.class, nodeDescription = "Route incoming messages based on the name of the device profile", - nodeDetails = "Route incoming messages based on the name of the device profile. The device profile name is case-sensitive", + nodeDetails = "Route incoming messages based on the name of the device profile. The device profile name is case-sensitive

" + + "Output connection types: Profile name of message originator or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbDeviceTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index 0bde9402a7..1b8461b44b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -22,6 +22,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.script.ScriptLanguage; @@ -32,7 +33,8 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; @Slf4j @RuleNode( type = ComponentType.FILTER, - name = "script", relationTypes = {"True", "False"}, + name = "script", + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, configClazz = TbJsFilterNodeConfiguration.class, nodeDescription = "Filter incoming messages using TBEL or JS script", nodeDetails = "Evaluates boolean function using incoming message. " + @@ -40,7 +42,8 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; "Script function should return boolean value and accepts three parameters:
" + "Message payload can be accessed via msg property. For example msg.temperature < 10;
" + "Message metadata can be accessed via metadata property. For example metadata.customerName === 'John';
" + - "Message type can be accessed via msgType property.", + "Message type can be accessed via msgType property.

" + + "Output connection types: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeScriptConfig" ) @@ -62,7 +65,7 @@ public class TbJsFilterNode implements TbNode { withCallback(scriptEngine.executeFilterAsync(msg), filterResult -> { ctx.logJsEvalResponse(); - ctx.tellNext(msg, filterResult ? "True" : "False"); + ctx.tellNext(msg, filterResult ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); }, t -> { ctx.tellFailure(msg, t); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index 706978846a..8c0f058ed3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -44,7 +44,8 @@ import java.util.Set; "If Array is empty - message not routed to next Node. " + "Message payload can be accessed via msg property. For example msg.temperature < 10;
" + "Message metadata can be accessed via metadata property. For example metadata.customerName === 'John';
" + - "Message type can be accessed via msgType property.", + "Message type can be accessed via msgType property.

" + + "Output connection types: Custom connection(s) defined by switch node or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeSwitchConfig") public class TbJsSwitchNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java index 765deb2ea7..5074991cb1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java @@ -21,6 +21,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -33,9 +34,10 @@ import org.thingsboard.server.common.msg.TbMsg; type = ComponentType.FILTER, name = "message type", configClazz = TbMsgTypeFilterNodeConfiguration.class, - relationTypes = {"True", "False"}, + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Filter incoming messages by Message Type", - nodeDetails = "If incoming MessageType is expected - send Message via True chain, otherwise False chain is used.", + nodeDetails = "If incoming message type is expected - send Message via True chain, otherwise False chain is used.

" + + "Output connection types: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeMessageTypeConfig") public class TbMsgTypeFilterNode implements TbNode { @@ -49,7 +51,7 @@ public class TbMsgTypeFilterNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - ctx.tellNext(msg, config.getMessageTypes().contains(msg.getType()) ? "True" : "False"); + ctx.tellNext(msg, config.getMessageTypes().contains(msg.getType()) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index 16649f8f85..99a3d41c3a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -19,24 +19,20 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.rule.engine.api.TbNode; 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.server.common.data.DataConstants; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; @Slf4j @RuleNode( type = ComponentType.FILTER, name = "message type switch", 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", "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"}, + relationTypes = {}, // should always be empty. We add the relation types for this node in AnnotationComponentDiscoveryService. nodeDescription = "Route incoming messages by Message Type", nodeDetails = "Sends messages with message types \"Post attributes\", \"Post telemetry\", \"RPC Request\" etc. via corresponding chain, otherwise Other chain is used.", uiResources = {"static/rulenode/rulenode-core-config.js"}, @@ -52,83 +48,7 @@ public class TbMsgTypeSwitchNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - String relationType; - if (msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) { - relationType = "Post attributes"; - } else if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { - relationType = "Post telemetry"; - } else if (msg.getType().equals(SessionMsgType.TO_SERVER_RPC_REQUEST.name())) { - relationType = "RPC Request from Device"; - } else if (msg.getType().equals(DataConstants.ACTIVITY_EVENT)) { - relationType = "Activity Event"; - } else if (msg.getType().equals(DataConstants.INACTIVITY_EVENT)) { - relationType = "Inactivity Event"; - } else if (msg.getType().equals(DataConstants.CONNECT_EVENT)) { - relationType = "Connect Event"; - } else if (msg.getType().equals(DataConstants.DISCONNECT_EVENT)) { - relationType = "Disconnect Event"; - } else if (msg.getType().equals(DataConstants.ENTITY_CREATED)) { - relationType = "Entity Created"; - } else if (msg.getType().equals(DataConstants.ENTITY_UPDATED)) { - relationType = "Entity Updated"; - } else if (msg.getType().equals(DataConstants.ENTITY_DELETED)) { - relationType = "Entity Deleted"; - } else if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED)) { - relationType = "Entity Assigned"; - } else if (msg.getType().equals(DataConstants.ENTITY_UNASSIGNED)) { - relationType = "Entity Unassigned"; - } else if (msg.getType().equals(DataConstants.ATTRIBUTES_UPDATED)) { - relationType = "Attributes Updated"; - } else if (msg.getType().equals(DataConstants.ATTRIBUTES_DELETED)) { - relationType = "Attributes Deleted"; - } else if (msg.getType().equals(DataConstants.ALARM_ACK)) { - 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)) { - relationType = "Entity Assigned From Tenant"; - } else if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED_TO_TENANT)) { - relationType = "Entity Assigned To Tenant"; - } else if (msg.getType().equals(DataConstants.TIMESERIES_UPDATED)) { - relationType = "Timeseries Updated"; - } else if (msg.getType().equals(DataConstants.TIMESERIES_DELETED)) { - relationType = "Timeseries Deleted"; - } else if (msg.getType().equals(DataConstants.RPC_QUEUED)) { - relationType = "RPC Queued"; - } else if (msg.getType().equals(DataConstants.RPC_SENT)) { - relationType = "RPC Sent"; - } else if (msg.getType().equals(DataConstants.RPC_DELIVERED)) { - relationType = "RPC Delivered"; - } else if (msg.getType().equals(DataConstants.RPC_SUCCESSFUL)) { - relationType = "RPC Successful"; - } else if (msg.getType().equals(DataConstants.RPC_TIMEOUT)) { - relationType = "RPC Timeout"; - } else if (msg.getType().equals(DataConstants.RPC_EXPIRED)) { - relationType = "RPC Expired"; - } else if (msg.getType().equals(DataConstants.RPC_FAILED)) { - relationType = "RPC Failed"; - } else if (msg.getType().equals(DataConstants.RPC_DELETED)) { - relationType = "RPC Deleted"; - } else if (msg.getType().equals(DataConstants.RELATION_ADD_OR_UPDATE)) { - relationType = "Relation Added or Updated"; - } else if (msg.getType().equals(DataConstants.RELATION_DELETED)) { - relationType = "Relation Deleted"; - } else if (msg.getType().equals(DataConstants.RELATIONS_DELETED)) { - relationType = "All Relations Deleted"; - } else { - relationType = "Other"; - } - ctx.tellNext(msg, relationType); + ctx.tellNext(msg, TbMsgType.getNodeConnection(msg.getType())); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java index f4e13d761e..8c32d8a1c9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java @@ -21,6 +21,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -31,9 +32,10 @@ import org.thingsboard.server.common.msg.TbMsg; type = ComponentType.FILTER, name = "entity type", configClazz = TbOriginatorTypeFilterNodeConfiguration.class, - relationTypes = {"True", "False"}, + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Filter incoming messages by the type of message originator entity", - nodeDetails = "Checks that the entity type of the incoming message originator matches one of the values specified in the filter.", + nodeDetails = "Checks that the entity type of the incoming message originator matches one of the values specified in the filter.

" + + "Output connection types: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeOriginatorTypeConfig") public class TbOriginatorTypeFilterNode implements TbNode { @@ -48,7 +50,7 @@ public class TbOriginatorTypeFilterNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { EntityType originatorType = msg.getOriginator().getEntityType(); - ctx.tellNext(msg, config.getOriginatorTypes().contains(originatorType) ? "True" : "False"); + ctx.tellNext(msg, config.getOriginatorTypes().contains(originatorType) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java index a920af5066..11365a2b1d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java @@ -19,8 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -29,55 +27,17 @@ import org.thingsboard.server.common.data.plugin.ComponentType; type = ComponentType.FILTER, name = "entity type switch", configClazz = EmptyNodeConfiguration.class, - relationTypes = {"Device", "Asset", "Alarm", "Entity View", "Tenant", "Customer", "User", "Dashboard", "Rule chain", "Rule node", "Edge"}, + relationTypes = {}, // should always be empty. We add the relation types for this node in AnnotationComponentDiscoveryService. nodeDescription = "Route incoming messages by Message Originator Type", - nodeDetails = "Routes messages to chain according to the entity type ('Device', 'Asset', etc.).", + nodeDetails = "Routes messages to chain according to the entity type ('Device', 'Asset', etc.).

" + + "Output connection types: entityType of the message originator or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbOriginatorTypeSwitchNode extends TbAbstractTypeSwitchNode { @Override - protected String getRelationType(TbContext ctx, EntityId originator) throws TbNodeException { - String relationType; - EntityType originatorType = originator.getEntityType(); - switch (originatorType) { - case TENANT: - relationType = "Tenant"; - break; - case CUSTOMER: - relationType = "Customer"; - break; - case USER: - relationType = "User"; - break; - case DASHBOARD: - relationType = "Dashboard"; - break; - case ASSET: - relationType = "Asset"; - break; - case DEVICE: - relationType = "Device"; - break; - case ENTITY_VIEW: - relationType = "Entity View"; - break; - case EDGE: - relationType = "Edge"; - break; - case RULE_CHAIN: - relationType = "Rule chain"; - break; - case RULE_NODE: - relationType = "Rule node"; - break; - case ALARM: - relationType = "Alarm"; - break; - default: - throw new TbNodeException("Unsupported originator type: " + originatorType); - } - return relationType; + protected String getRelationType(TbContext ctx, EntityId originator) { + return originator.getEntityType().getNormalName(); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java index 35baaf461b..e0ffc564db 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java @@ -21,7 +21,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -48,7 +48,7 @@ public class TbCheckpointNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - ctx.enqueueForTellNext(msg, queueName, TbRelationTypes.SUCCESS, () -> ctx.ack(msg), error -> ctx.tellFailure(msg, error)); + ctx.enqueueForTellNext(msg, queueName, TbNodeConnectionType.SUCCESS, () -> ctx.ack(msg), error -> ctx.tellFailure(msg, error)); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java index 373118ebdd..b4d217d799 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -30,7 +31,7 @@ import org.thingsboard.server.common.msg.TbMsg; type = ComponentType.FILTER, name = "gps geofencing filter", configClazz = TbGpsGeofencingFilterNodeConfiguration.class, - relationTypes = {"True", "False"}, + relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Filter incoming messages by GPS based geofencing", nodeDetails = "Extracts latitude and longitude parameters from the incoming message and checks them according to configured perimeter.
" + "Configuration:

" + @@ -57,14 +58,15 @@ import org.thingsboard.server.common.msg.TbMsg; "

" + "{\"latitude\": 48.198618758582384, \"longitude\": 24.65322245153503, \"radius\": 100.0, \"radiusUnit\": \"METER\" }" + "

" + - "Available radius units: METER, KILOMETER, FOOT, MILE, NAUTICAL_MILE;", + "Available radius units: METER, KILOMETER, FOOT, MILE, NAUTICAL_MILE;

" + + "Output connection types: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeGpsGeofencingConfig") public class TbGpsGeofencingFilterNode extends AbstractGeofencingNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { - ctx.tellNext(msg, checkMatches(msg) ? "True" : "False"); + ctx.tellNext(msg, checkMatches(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index de1abea224..7afd61da2d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -31,7 +31,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.exception.ThingsboardKafkaClientError; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -165,7 +165,7 @@ public class TbKafkaNode implements TbNode { private void processRecord(TbContext ctx, TbMsg msg, RecordMetadata metadata, Exception e) { if (e == null) { TbMsg next = processResponse(ctx, msg, metadata); - ctx.tellNext(next, TbRelationTypes.SUCCESS); + ctx.tellNext(next, TbNodeConnectionType.SUCCESS); } else { TbMsg next = processException(ctx, msg, e); ctx.tellFailure(next, e); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index 2af4978766..d56b0d6890 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -27,7 +27,6 @@ import org.thingsboard.rule.engine.api.TbNode; 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.server.common.data.StringUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -35,7 +34,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; +import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; @Slf4j diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java index 14227cadc4..5707d64071 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java @@ -49,7 +49,7 @@ public abstract class TbAbstractGetEntityDetailsNode detailsList) throws TbNodeException { if (detailsList == null || detailsList.isEmpty()) { - throw new TbNodeException("No entity details selected!"); + throw new TbNodeException("At least one entity detail should be selected!"); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index 31f661417b..4397200fe4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -40,7 +40,6 @@ import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.rule.RuleNodeState; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.dao.sql.query.EntityKeyMapping; @@ -55,6 +54,18 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_ACK; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_CLEAR; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UNASSIGNED; +import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; + @Slf4j class DeviceState { @@ -136,24 +147,24 @@ class DeviceState { latestValues = fetchLatestValues(ctx, deviceId); } boolean stateChanged = false; - if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { + if (msg.getType().equals(POST_TELEMETRY_REQUEST.name())) { stateChanged = processTelemetry(ctx, msg); - } else if (msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) { + } else if (msg.getType().equals(POST_ATTRIBUTES_REQUEST.name())) { stateChanged = processAttributesUpdateRequest(ctx, msg); - } else if (msg.getType().equals(DataConstants.ACTIVITY_EVENT) || msg.getType().equals(DataConstants.INACTIVITY_EVENT)) { + } else if (msg.getType().equals(ACTIVITY_EVENT.name()) || msg.getType().equals(INACTIVITY_EVENT.name())) { stateChanged = processDeviceActivityEvent(ctx, msg); - } else if (msg.getType().equals(DataConstants.ATTRIBUTES_UPDATED)) { + } else if (msg.getType().equals(ATTRIBUTES_UPDATED.name())) { stateChanged = processAttributesUpdateNotification(ctx, msg); - } else if (msg.getType().equals(DataConstants.ATTRIBUTES_DELETED)) { + } else if (msg.getType().equals(ATTRIBUTES_DELETED.name())) { stateChanged = processAttributesDeleteNotification(ctx, msg); - } else if (msg.getType().equals(DataConstants.ALARM_CLEAR)) { + } else if (msg.getType().equals(ALARM_CLEAR.name())) { stateChanged = processAlarmClearNotification(ctx, msg); - } else if (msg.getType().equals(DataConstants.ALARM_ACK)) { + } else if (msg.getType().equals(ALARM_ACK.name())) { processAlarmAckNotification(ctx, msg); - } else if (msg.getType().equals(DataConstants.ALARM_DELETE)) { + } else if (msg.getType().equals(ALARM_DELETE.name())) { processAlarmDeleteNotification(ctx, msg); } else { - if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED) || msg.getType().equals(DataConstants.ENTITY_UNASSIGNED)) { + if (msg.getType().equals(ENTITY_ASSIGNED.name()) || msg.getType().equals(ENTITY_UNASSIGNED.name())) { dynamicPredicateValueCtx.resetCustomer(); } ctx.tellSuccess(msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index ba987678f3..a8b3ef4f5f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -26,7 +26,6 @@ import org.thingsboard.rule.engine.api.TbNode; 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.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; @@ -46,6 +45,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UPDATED; + @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -123,10 +125,10 @@ public class TbDeviceProfileNode implements TbNode { } else { if (EntityType.DEVICE.equals(originatorType)) { DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); - if (msg.getType().equals(DataConstants.ENTITY_UPDATED)) { + if (msg.getType().equals(ENTITY_UPDATED.name())) { invalidateDeviceProfileCache(deviceId, msg.getData()); ctx.tellSuccess(msg); - } else if (msg.getType().equals(DataConstants.ENTITY_DELETED)) { + } else if (msg.getType().equals(ENTITY_DELETED.name())) { removeDeviceState(deviceId); ctx.tellSuccess(msg); } else { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 6f9541f5b1..c7580fb7d3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -43,7 +43,7 @@ import org.springframework.web.util.UriComponentsBuilder; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.credentials.BasicCredentials; import org.thingsboard.rule.engine.credentials.ClientCredentials; @@ -212,7 +212,7 @@ public class TbHttpClient { ctx.tellSuccess(next); } else { TbMsg next = processFailureResponse(ctx, msg, responseEntity); - ctx.tellNext(next, TbRelationTypes.FAILURE); + ctx.tellNext(next, TbNodeConnectionType.FAILURE); } } }); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java index 79bd6f9bf9..936b192c85 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java @@ -27,12 +27,13 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -76,7 +77,7 @@ public class TbSendRPCRequestNode implements TbNode { ctx.tellFailure(msg, new RuntimeException("Params are not present in the message!")); } else { int requestId = json.has("requestId") ? json.get("requestId").getAsInt() : random.nextInt(); - boolean restApiCall = msg.getType().equals(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE); + boolean restApiCall = msg.getType().equals(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.name()); tmp = msg.getMetaData().getValue("oneway"); boolean oneway = !StringUtils.isEmpty(tmp) && Boolean.parseBoolean(tmp); @@ -117,7 +118,7 @@ public class TbSendRPCRequestNode implements TbNode { ctx.getRpcService().sendRpcRequestToDevice(request, ruleEngineDeviceRpcResponse -> { if (ruleEngineDeviceRpcResponse.getError().isEmpty()) { TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), ruleEngineDeviceRpcResponse.getResponse().orElse("{}")); - ctx.enqueueForTellNext(next, TbRelationTypes.SUCCESS); + ctx.enqueueForTellNext(next, TbNodeConnectionType.SUCCESS); } else { TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), wrap("error", ruleEngineDeviceRpcResponse.getError().get().name())); ctx.enqueueForTellFailure(next, ruleEngineDeviceRpcResponse.getError().get().name()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java index 7bf08e8647..d96060ffc2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java @@ -22,7 +22,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -75,7 +75,7 @@ public abstract class TbAbstractTransformNode implements TbNode { ctx.tellFailure(msg, e); } }); - msgs.forEach(newMsg -> ctx.enqueueForTellNext(newMsg, TbRelationTypes.SUCCESS, wrapper::onSuccess, wrapper::onFailure)); + msgs.forEach(newMsg -> ctx.enqueueForTellNext(newMsg, TbNodeConnectionType.SUCCESS, wrapper::onSuccess, wrapper::onFailure)); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java index ae7ceade97..9c86891bc0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java @@ -25,7 +25,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -79,7 +79,7 @@ public class TbSplitArrayMsgNode implements TbNode { }); data.forEach(msgNode -> { TbMsg outMsg = TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(msgNode)); - ctx.enqueueForTellNext(outMsg, TbRelationTypes.SUCCESS, wrapper::onSuccess, wrapper::onFailure); + ctx.enqueueForTellNext(outMsg, TbNodeConnectionType.SUCCESS, wrapper::onSuccess, wrapper::onFailure); }); } } else { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java index 22df15631e..3b8a8c9193 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java @@ -28,8 +28,8 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbRelationTypes; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; @@ -54,6 +54,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; @RunWith(MockitoJUnitRunner.class) public class TbCreateRelationNodeTest { @@ -111,7 +112,7 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) .thenReturn(Futures.immediateFuture(false)); @@ -119,7 +120,7 @@ public class TbCreateRelationNodeTest { .thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); - verify(ctx).tellNext(msg, TbRelationTypes.SUCCESS); + verify(ctx).tellNext(msg, TbNodeConnectionType.SUCCESS); } @Test @@ -138,7 +139,7 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); EntityRelation relation = new EntityRelation(); when(ctx.getRelationService().findByToAndTypeAsync(any(), eq(msg.getOriginator()), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) @@ -150,7 +151,7 @@ public class TbCreateRelationNodeTest { .thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); - verify(ctx).tellNext(msg, TbRelationTypes.SUCCESS); + verify(ctx).tellNext(msg, TbNodeConnectionType.SUCCESS); } @Test @@ -169,7 +170,7 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) .thenReturn(Futures.immediateFuture(false)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java index 84deafe7e4..98c0c62231 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java @@ -49,10 +49,18 @@ import java.util.UUID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; @RunWith(MockitoJUnitRunner.class) public class TbMsgPushToEdgeNodeTest { + private static final List MISC_EVENTS = List.of(CONNECT_EVENT.name(), DISCONNECT_EVENT.name(), + ACTIVITY_EVENT.name(), INACTIVITY_EVENT.name()); + TbMsgPushToEdgeNode node; private final TenantId tenantId = TenantId.fromUUID(UUID.randomUUID()); @@ -102,7 +110,7 @@ public class TbMsgPushToEdgeNodeTest { PageData edgePageData = new PageData<>(List.of(edgeId), 1, 1, false); Mockito.when(edgeService.findRelatedEdgeIdsByEntityId(tenantId, userId, new PageLink(TbMsgPushToEdgeNode.DEFAULT_PAGE_SIZE))).thenReturn(edgePageData); - TbMsg msg = TbMsg.newMsg(DataConstants.ATTRIBUTES_UPDATED, userId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(ATTRIBUTES_UPDATED.name(), userId, new TbMsgMetaData(), TbMsgDataType.JSON, "{}", null, null); node.onMsg(ctx, msg); @@ -112,9 +120,7 @@ public class TbMsgPushToEdgeNodeTest { @Test public void testMiscEventsProcessedAsAttributesUpdated() { - List miscEvents = List.of(DataConstants.CONNECT_EVENT, DataConstants.DISCONNECT_EVENT, - DataConstants.ACTIVITY_EVENT, DataConstants.INACTIVITY_EVENT); - for (String event : miscEvents) { + for (String event : MISC_EVENTS) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue(DataConstants.SCOPE, DataConstants.SERVER_SCOPE); testEvent(event, metaData, EdgeEventActionType.ATTRIBUTES_UPDATED, "kv"); @@ -123,9 +129,7 @@ public class TbMsgPushToEdgeNodeTest { @Test public void testMiscEventsProcessedAsTimeseriesUpdated() { - List miscEvents = List.of(DataConstants.CONNECT_EVENT, DataConstants.DISCONNECT_EVENT, - DataConstants.ACTIVITY_EVENT, DataConstants.INACTIVITY_EVENT); - for (String event : miscEvents) { + for (String event : MISC_EVENTS) { testEvent(event, new TbMsgMetaData(), EdgeEventActionType.TIMESERIES_UPDATED, "data"); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 4cfe38f3fc..81e4c70ce0 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -27,6 +27,8 @@ import org.thingsboard.rule.engine.api.ScriptEngine; 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.TbNodeConnectionType; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.script.ScriptLanguage; @@ -55,21 +57,21 @@ public class TbJsFilterNodeTest { private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); @Test - public void falseEvaluationDoNotSendMsg() throws TbNodeException, ScriptException { + public void falseEvaluationDoNotSendMsg() throws TbNodeException { initWithScript(); - TbMsg msg = TbMsg.newMsg("USER", null, new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(false)); node.onMsg(ctx, msg); verify(ctx).getDbCallbackExecutor(); - verify(ctx).tellNext(msg, "False"); + verify(ctx).tellNext(msg, TbNodeConnectionType.FALSE); } @Test public void exceptionInJsThrowsException() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFailedFuture(new ScriptException("error"))); @@ -81,12 +83,12 @@ public class TbJsFilterNodeTest { public void metadataConditionCanBeTrue() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); verify(ctx).getDbCallbackExecutor(); - verify(ctx).tellNext(msg, "True"); + verify(ctx).tellNext(msg, TbNodeConnectionType.TRUE); } private void initWithScript() throws TbNodeException { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java index d33b2af030..b480719f64 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java @@ -22,7 +22,6 @@ import org.mockito.ArgumentCaptor; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; @@ -64,6 +63,10 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_CLEAR; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class DeviceStateTest { @@ -115,11 +118,11 @@ public class DeviceStateTest { verify(ctx).enqueueForTellNext(resultMsgCaptor.capture(), eq("Alarm Created")); Alarm alarm = JacksonUtil.fromString(resultMsgCaptor.getValue().getData(), Alarm.class); - deviceState.process(ctx, TbMsg.newMsg(DataConstants.ALARM_CLEAR, deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); + deviceState.process(ctx, TbMsg.newMsg(ALARM_CLEAR.name(), deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); reset(ctx); String deletedAttributes = "{ \"attributes\": [ \"other\" ] }"; - deviceState.process(ctx, TbMsg.newMsg(DataConstants.ATTRIBUTES_DELETED, deviceId, new TbMsgMetaData(), deletedAttributes)); + deviceState.process(ctx, TbMsg.newMsg(ATTRIBUTES_DELETED.name(), deviceId, new TbMsgMetaData(), deletedAttributes)); verify(ctx, never()).enqueueForTellNext(any(), anyString()); } @@ -129,7 +132,7 @@ public class DeviceStateTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); DeviceState deviceState = createDeviceState(deviceId, alarmConfig); - TbMsg attributeUpdateMsg = TbMsg.newMsg(SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), + TbMsg attributeUpdateMsg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), deviceId, new TbMsgMetaData(), "{ \"enabled\": false }"); deviceState.process(ctx, attributeUpdateMsg); @@ -137,9 +140,9 @@ public class DeviceStateTest { verify(ctx).enqueueForTellNext(resultMsgCaptor.capture(), eq("Alarm Created")); Alarm alarm = JacksonUtil.fromString(resultMsgCaptor.getValue().getData(), Alarm.class); - deviceState.process(ctx, TbMsg.newMsg(DataConstants.ALARM_CLEAR, deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); + deviceState.process(ctx, TbMsg.newMsg(ALARM_CLEAR.name(), deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); - TbMsg alarmDeleteNotification = TbMsg.newMsg(DataConstants.ALARM_DELETE, deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm)); + TbMsg alarmDeleteNotification = TbMsg.newMsg(ALARM_DELETE.name(), deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm)); assertDoesNotThrow(() -> { deviceState.process(ctx, alarmDeleteNotification); }); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java index 7bfa5c8bfc..f48a9f670c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java @@ -30,7 +30,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; 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.TbRelationTypes; +import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.deduplication.DeduplicationStrategy; import org.thingsboard.rule.engine.deduplication.TbMsgDeduplicationNode; import org.thingsboard.rule.engine.deduplication.TbMsgDeduplicationNodeConfiguration; @@ -173,7 +173,7 @@ public class TbMsgDeduplicationNodeTest { verify(ctx, times(msgCount)).ack(any()); verify(ctx, times(1)).tellFailure(eq(msgToReject), any()); verify(node, times(msgCount + wantedNumberOfTellSelfInvocation + 1)).onMsg(eq(ctx), any()); - verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); + verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.SUCCESS), successCaptor.capture(), failureCaptor.capture()); TbMsg firstMsg = inputMsgs.get(0); TbMsg actualMsg = newMsgCaptor.getValue(); @@ -221,7 +221,7 @@ public class TbMsgDeduplicationNodeTest { verify(ctx, times(msgCount)).ack(any()); verify(ctx, times(1)).tellFailure(eq(msgToReject), any()); verify(node, times(msgCount + wantedNumberOfTellSelfInvocation + 1)).onMsg(eq(ctx), any()); - verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); + verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.SUCCESS), successCaptor.capture(), failureCaptor.capture()); TbMsg actualMsg = newMsgCaptor.getValue(); // msg ids should be different because we create new msg before enqueueForTellNext @@ -263,7 +263,7 @@ public class TbMsgDeduplicationNodeTest { verify(ctx, times(msgCount)).ack(any()); verify(node, times(msgCount + wantedNumberOfTellSelfInvocation)).onMsg(eq(ctx), any()); - verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); + verify(ctx, times(1)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.SUCCESS), successCaptor.capture(), failureCaptor.capture()); Assertions.assertEquals(1, newMsgCaptor.getAllValues().size()); TbMsg outMessage = newMsgCaptor.getAllValues().get(0); @@ -309,7 +309,7 @@ public class TbMsgDeduplicationNodeTest { verify(ctx, times(msgCount)).ack(any()); verify(node, times(msgCount + wantedNumberOfTellSelfInvocation)).onMsg(eq(ctx), any()); - verify(ctx, times(2)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); + verify(ctx, times(2)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.SUCCESS), successCaptor.capture(), failureCaptor.capture()); List resultMsgs = newMsgCaptor.getAllValues(); Assertions.assertEquals(2, resultMsgs.size()); @@ -363,7 +363,7 @@ public class TbMsgDeduplicationNodeTest { verify(ctx, times(msgCount)).ack(any()); verify(node, times(msgCount + wantedNumberOfTellSelfInvocation)).onMsg(eq(ctx), any()); - verify(ctx, times(2)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbRelationTypes.SUCCESS), successCaptor.capture(), failureCaptor.capture()); + verify(ctx, times(2)).enqueueForTellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.SUCCESS), successCaptor.capture(), failureCaptor.capture()); List resultMsgs = newMsgCaptor.getAllValues(); Assertions.assertEquals(2, resultMsgs.size()); From 11cb696d5c8e110c5f77c45a8fa9e558294638c8 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 21 Jun 2023 16:04:23 +0300 Subject: [PATCH 007/166] added conroller method to retrieve list of commands to publish telemetry --- .../server/controller/DeviceController.java | 27 +++++ .../server/dao/device/DeviceService.java | 2 + .../server/dao/device/DeviceServiceImpl.java | 99 +++++++++++++++++++ 3 files changed, 128 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index bb34f6d5b2..36798b5b75 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -73,8 +73,12 @@ import org.thingsboard.server.service.entitiy.device.TbDeviceService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -125,6 +129,8 @@ public class DeviceController extends BaseController { private final TbDeviceService tbDeviceService; + private final SystemSecurityService systemSecurityService; + @ApiOperation(value = "Get Device (getDeviceById)", notes = "Fetch the Device object based on the provided Device Id. " + "If the user has the authority of 'TENANT_ADMIN', the server checks that the device is owned by the same tenant. " + @@ -155,6 +161,27 @@ public class DeviceController extends BaseController { return checkDeviceInfoId(deviceId, Operation.READ); } + @ApiOperation(value = "Get commands to publish device telemetry (getDevicePublishTelemetryCommands)", + notes = "Fetch the list of commands to publish device telemetry based on device profile " + + "If the user has the authority of 'Tenant Administrator', the server checks that the device is owned by the same tenant. " + + "If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer. " + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/device/info/{deviceId}/commands", method = RequestMethod.GET) + @ResponseBody + public List getDevicePublishTelemetryCommands(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException { + checkParameter(DEVICE_ID, strDeviceId); + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS); + URI baseUri = new URI(systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request)); + List commands = deviceService.findDevicePublishTelemetryCommands(device); + return commands.stream() + .map(s -> s.replace("$THINGSBOARD_HOST_NAME", baseUri.getHost()) + .replace("$THINGSBOARD_BASE_URL", baseUri.toString())) + .collect(Collectors.toList()); + } + @ApiOperation(value = "Create Or Update Device (saveDevice)", notes = "Create or update the Device. When creating device, platform generates Device Id as " + UUID_WIKI_LINK + "Device credentials are also generated if not provided in the 'accessToken' request parameter. " + diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index a90ea9a572..d8e2a62040 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -43,6 +43,8 @@ public interface DeviceService extends EntityDaoService { DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId); + List findDevicePublishTelemetryCommands(Device device); + Device findDeviceById(TenantId tenantId, DeviceId deviceId); ListenableFuture findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 46830f8f76..3f52ec5afe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -38,6 +39,7 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration; @@ -47,6 +49,10 @@ import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; @@ -122,6 +128,67 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDevicePublishTelemetryCommands(Device device) { + DeviceId deviceId = device.getId(); + log.trace("Executing findDevicePublishTelemetryCommands [{}]", deviceId); + validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); + DeviceCredentialsType credentialsType = deviceCredentials.getCredentialsType(); + + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); + + ArrayList commands = new ArrayList<>(); + switch (deviceProfile.getTransportType()) { + case DEFAULT: + switch (credentialsType) { + case ACCESS_TOKEN: + commands.add(getMqttAccessTokenCommand(deviceCredentials) + " -m {temperature:15}"); + commands.add(getHttpAccessTokenCommand(deviceCredentials) + " --data \"{temperature:16}\""); + commands.add("echo -n {temperature:17} | " + getCoapAccessTokenCommand(deviceCredentials) + " -f-"); + break; + case MQTT_BASIC: + commands.add(getMqttBasicPublishCommand(deviceCredentials) + " -m {temperature:18}"); + break; + case X509_CERTIFICATE: + commands.add(getMqttX509Command() + " -m {temperature:19}"); + break; + } + break; + case MQTT: + MqttDeviceProfileTransportConfiguration transportConfiguration = + (MqttDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + TransportPayloadType payloadType = transportConfiguration.getTransportPayloadTypeConfiguration().getTransportPayloadType(); + String payload = (payloadType == TransportPayloadType.PROTOBUF) ? " -f protobufFileName" : " -m {temperature:25}"; + switch (credentialsType) { + case ACCESS_TOKEN: + commands.add(getMqttAccessTokenCommand(deviceCredentials) + payload); + break; + case MQTT_BASIC: + commands.add(getMqttBasicPublishCommand(deviceCredentials) + payload); + break; + case X509_CERTIFICATE: + commands.add(getMqttX509Command() + payload); + break; + } + break; + case COAP: + CoapDeviceProfileTransportConfiguration coapTransportConfiguration = + (CoapDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + CoapDeviceTypeConfiguration coapConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); + if (coapConfiguration instanceof DefaultCoapDeviceTypeConfiguration) { + DefaultCoapDeviceTypeConfiguration configuration = + (DefaultCoapDeviceTypeConfiguration) coapTransportConfiguration.getCoapDeviceTypeConfiguration(); + TransportPayloadType transportPayloadType = configuration.getTransportPayloadTypeConfiguration().getTransportPayloadType(); + String payloadExample = (transportPayloadType == TransportPayloadType.PROTOBUF) ? " -t binary -f protobufFileName" : " -t json -f jsonFileName"; + commands.add(getCoapAccessTokenCommand(deviceCredentials) + payloadExample); + } + break; + } + return commands; + } + @Override public Device findDeviceById(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceById [{}]", deviceId); @@ -681,4 +748,36 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Wed, 21 Jun 2023 18:37:27 +0300 Subject: [PATCH 008/166] refactoring --- .../server/controller/DeviceController.java | 10 ++-- .../server/dao/device/DeviceService.java | 3 +- .../server/dao/device/DeviceServiceImpl.java | 46 ++++++++++--------- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 36798b5b75..6d027c5c5d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -77,7 +77,6 @@ import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; -import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; @@ -174,12 +173,9 @@ public class DeviceController extends BaseController { checkParameter(DEVICE_ID, strDeviceId); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS); - URI baseUri = new URI(systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request)); - List commands = deviceService.findDevicePublishTelemetryCommands(device); - return commands.stream() - .map(s -> s.replace("$THINGSBOARD_HOST_NAME", baseUri.getHost()) - .replace("$THINGSBOARD_BASE_URL", baseUri.toString())) - .collect(Collectors.toList()); + + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); + return deviceService.findDevicePublishTelemetryCommands(baseUrl, device); } @ApiOperation(value = "Create Or Update Device (saveDevice)", diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index d8e2a62040..79f4781936 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.entity.EntityDaoService; +import java.net.URISyntaxException; import java.util.List; import java.util.UUID; @@ -43,7 +44,7 @@ public interface DeviceService extends EntityDaoService { DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId); - List findDevicePublishTelemetryCommands(Device device); + List findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException; Device findDeviceById(TenantId tenantId, DeviceId deviceId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 3f52ec5afe..85aab629d8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -19,7 +19,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -80,6 +79,8 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -129,11 +130,12 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDevicePublishTelemetryCommands(Device device) { + public List findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException { DeviceId deviceId = device.getId(); log.trace("Executing findDevicePublishTelemetryCommands [{}]", deviceId); validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); + String hostname = new URI(baseUrl).getHost(); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); DeviceCredentialsType credentialsType = deviceCredentials.getCredentialsType(); @@ -144,15 +146,15 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Thu, 22 Jun 2023 15:27:45 +0300 Subject: [PATCH 009/166] added tests --- .../server/controller/DeviceController.java | 2 +- .../controller/DeviceControllerTest.java | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 6d027c5c5d..e473163642 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -166,7 +166,7 @@ public class DeviceController extends BaseController { "If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/device/info/{deviceId}/commands", method = RequestMethod.GET) + @RequestMapping(value = "/device/{deviceId}/commands", method = RequestMethod.GET) @ResponseBody public List getDevicePublishTelemetryCommands(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException { diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 11d11eccdd..00ccdb1119 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -40,6 +40,8 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackageInfo; @@ -49,6 +51,10 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceCredentialsId; @@ -643,6 +649,59 @@ public class DeviceControllerTest extends AbstractControllerTest { Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); } + @Test + public void testFetchPublishTelemetryCommandsForDefaultDevice() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setType("default"); + Device savedDevice = doPost("/api/device", device, Device.class); + List commands = + doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); + + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + assertThat(commands).hasSize(3); + assertThat(commands).containsExactly(String.format("mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -u %s -m \"{temperature:15}\"", + credentials.getCredentialsId()), + String.format("curl -v -X POST http://localhost:80/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:16}\"", + credentials.getCredentialsId()), + String.format("echo -n \"{temperature:17}\" | coap-client -m post coap://localhost:5683/api/v1/%s/telemetry -f-", + credentials.getCredentialsId())); + } + + @Test + public void testFetchPublishTelemetryCommandsForMqttDevice() throws Exception { + DeviceProfile mqttProfile = new DeviceProfile(); + mqttProfile.setName("Mqtt device profile"); + mqttProfile.setType(DeviceProfileType.DEFAULT); + mqttProfile.setTransportType(DeviceTransportType.MQTT); + + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); + deviceProfileData.setTransportConfiguration(new MqttDeviceProfileTransportConfiguration()); + + mqttProfile.setProfileData(deviceProfileData); + mqttProfile.setDefault(false); + mqttProfile.setDefaultRuleChainId(null); + + mqttProfile = doPost("/api/deviceProfile", mqttProfile, DeviceProfile.class); + + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(mqttProfile.getId()); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + List commands = + doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); + assertThat(commands).hasSize(1); + assertThat(commands.get(0)).isEqualTo("mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -u " + + credentials.getCredentialsId() + " -m \"{temperature:25}\""); + } + @Test public void testSaveDeviceCredentials() throws Exception { Device device = new Device(); From a3c085027758c0fa4850dff2f44a0e96e1ed1567 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 23 Jun 2023 19:00:25 +0300 Subject: [PATCH 010/166] added tests for TbCheckAlarmStatusNode && TbMsgTypeFilterNodeTest && TbOriginatorTypeFilterNodeTest && replaced SessionMsgType && refactoring --- .../actors/ruleChain/DefaultTbContext.java | 2 +- .../RuleChainActorMessageProcessor.java | 2 +- .../AnnotationComponentDiscoveryService.java | 4 +- .../processor/device/DeviceEdgeProcessor.java | 4 +- .../telemetry/BaseTelemetryProcessor.java | 7 +- .../update/DefaultDataUpdateService.java | 3 +- ...AbstractRuleEngineFlowIntegrationTest.java | 5 +- .../RuleChainMsgConstructorTest.java | 126 ++++++------- .../script/NashornJsInvokeServiceTest.java | 3 +- .../service/script/TbelInvokeServiceTest.java | 3 +- .../SequentialTimeseriesPersistenceTest.java | 4 +- .../sync/ie/BaseExportImportServiceTest.java | 5 +- .../server/common/data/msg/TbMsgType.java | 18 +- .../data/msg}/TbNodeConnectionType.java | 4 +- .../common/msg/session/SessionMsgType.java | 48 ----- .../transport/coap/CoapSessionMsgType.java | 31 ++++ .../transport/coap/CoapTransportResource.java | 27 ++- .../coap/client/CoapClientContext.java | 4 +- .../coap/client/DefaultCoapClientContext.java | 4 +- .../service/DefaultTransportService.java | 14 +- .../server/dao/sql/event/JpaBaseEventDao.java | 5 +- .../thingsboard/rule/engine/api/RuleNode.java | 3 +- .../engine/action/TbAbstractAlarmNode.java | 2 +- .../action/TbAbstractRelationActionNode.java | 4 +- .../TbCopyAttributesToEntityViewNode.java | 2 +- .../rule/engine/action/TbMsgCountNode.java | 6 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 2 +- .../deduplication/TbMsgDeduplicationNode.java | 2 +- .../rule/engine/delay/TbMsgDelayNode.java | 2 +- .../engine/filter/TbAssetTypeSwitchNode.java | 9 +- .../engine/filter/TbCheckAlarmStatusNode.java | 4 +- .../engine/filter/TbCheckMessageNode.java | 13 +- .../TbCheckMessageNodeConfiguration.java | 2 +- .../engine/filter/TbCheckRelationNode.java | 16 +- .../TbCheckRelationNodeConfiguration.java | 3 +- .../engine/filter/TbDeviceTypeSwitchNode.java | 5 +- .../rule/engine/filter/TbJsFilterNode.java | 4 +- .../rule/engine/filter/TbJsSwitchNode.java | 2 +- .../engine/filter/TbMsgTypeFilterNode.java | 6 +- .../TbMsgTypeFilterNodeConfiguration.java | 13 +- .../engine/filter/TbMsgTypeSwitchNode.java | 6 +- .../filter/TbOriginatorTypeFilterNode.java | 6 +- .../filter/TbOriginatorTypeSwitchNode.java | 2 +- .../rule/engine/flow/TbCheckpointNode.java | 2 +- .../engine/geo/TbGpsGeofencingFilterNode.java | 4 +- .../rule/engine/kafka/TbKafkaNode.java | 2 +- .../rule/engine/mail/TbMsgToEmailNode.java | 2 +- .../engine/metadata/CalculateDeltaNode.java | 11 +- .../rule/engine/rest/TbHttpClient.java | 2 +- .../rule/engine/rpc/TbSendRPCRequestNode.java | 2 +- .../engine/telemetry/TbMsgAttributesNode.java | 4 +- .../engine/telemetry/TbMsgTimeseriesNode.java | 5 +- .../transform/TbAbstractTransformNode.java | 2 +- .../engine/transform/TbSplitArrayMsgNode.java | 2 +- .../rule/engine/TestDbCallbackExecutor.java | 40 +++++ .../rule/engine/action/TbAlarmNodeTest.java | 19 +- .../action/TbCreateRelationNodeTest.java | 20 +-- .../engine/edge/TbMsgPushToEdgeNodeTest.java | 4 +- .../filter/TbAssetTypeSwitchNodeTest.java | 3 +- .../filter/TbCheckAlarmStatusNodeTest.java | 167 ++++++++++++++++++ .../filter/TbDeviceTypeSwitchNodeTest.java | 3 +- .../engine/filter/TbJsFilterNodeTest.java | 2 +- .../filter/TbMsgTypeFilterNodeTest.java | 103 +++++++++++ .../TbOriginatorTypeFilterNodeTest.java | 102 +++++++++++ .../metadata/CalculateDeltaNodeTest.java | 55 +++--- .../TbFetchDeviceCredentialsNodeTest.java | 3 +- .../TbGetCustomerAttributeNodeTest.java | 27 +-- .../TbGetCustomerDetailsNodeTest.java | 23 +-- .../TbGetOriginatorFieldsNodeTest.java | 30 +--- .../TbGetRelatedAttributeNodeTest.java | 26 +-- .../TbGetTenantAttributeNodeTest.java | 26 +-- .../metadata/TbGetTenantDetailsNodeTest.java | 3 +- .../rule/engine/profile/DeviceStateTest.java | 3 +- .../profile/TbDeviceProfileNodeTest.java | 48 ++--- .../engine/rpc/TbSendRPCReplyNodeTest.java | 6 +- .../TbMsgDeleteAttributesNodeTest.java | 3 +- .../transform/TbChangeOriginatorNodeTest.java | 19 +- .../engine/transform/TbCopyKeysNodeTest.java | 3 +- .../transform/TbDeleteKeysNodeTest.java | 3 +- .../engine/transform/TbJsonPathNodeTest.java | 3 +- .../transform/TbMsgDeduplicationNodeTest.java | 11 +- .../transform/TbRenameKeysNodeTest.java | 3 +- .../transform/TbSplitArrayMsgNodeTest.java | 3 +- .../EntitiesCustomerIdAsyncLoaderTest.java | 20 +-- .../util/EntitiesFieldsAsyncLoaderTest.java | 20 +-- ...ntitiesRelatedDeviceIdAsyncLoaderTest.java | 20 +-- ...ntitiesRelatedEntityIdAsyncLoaderTest.java | 19 +- 87 files changed, 769 insertions(+), 546 deletions(-) rename {rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api => common/data/src/main/java/org/thingsboard/server/common/data/msg}/TbNodeConnectionType.java (90%) delete mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java create mode 100644 common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapSessionMsgType.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/TestDbCallbackExecutor.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 778a3db51f..2a15def2f7 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -33,7 +33,7 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.util.TenantIdLoader; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index 351a012d73..3c8ddb2a7b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -16,7 +16,7 @@ package org.thingsboard.server.actors.ruleChain; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorRef; diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index e8fff7ba60..112c406d19 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -31,7 +31,7 @@ import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeDefinition; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.server.common.data.msg.TbMsgType; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbVersionedNode; import org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode; import org.thingsboard.rule.engine.filter.TbOriginatorTypeSwitchNode; @@ -253,7 +253,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe } if (TbMsgTypeSwitchNode.class.equals(clazz)) { relationTypes.addAll(TbMsgType.NODE_CONNECTIONS); - relationTypes.add(TbMsgType.OTHER); + relationTypes.add(TbNodeConnectionType.OTHER); } if (!relationTypes.contains(TbNodeConnectionType.FAILURE)) { relationTypes.add(TbNodeConnectionType.FAILURE); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java index 0d48f1532f..3848828522 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rpc.RpcError; @@ -46,7 +47,6 @@ 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.rpc.FromDeviceRpcResponse; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; @@ -218,7 +218,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { ObjectNode data = JacksonUtil.newObjectNode(); data.put("method", deviceRpcCallMsg.getRequestMsg().getMethod()); data.put("params", deviceRpcCallMsg.getRequestMsg().getParams()); - TbMsg tbMsg = TbMsg.newMsg(SessionMsgType.TO_SERVER_RPC_REQUEST.name(), deviceId, null, metaData, + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.TO_SERVER_RPC_REQUEST.name(), deviceId, null, metaData, TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(data)); tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { @Override diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index 6a7ffe2c40..c55a931194 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -54,7 +54,6 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.model.ModelConstants; @@ -74,6 +73,8 @@ import java.util.List; import java.util.UUID; import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @Slf4j public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { @@ -186,7 +187,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); metaData.putValue("ts", tsKv.getTs() + ""); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), POST_TELEMETRY_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { @@ -230,7 +231,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { SettableFuture futureToSet = SettableFuture.create(); JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), POST_ATTRIBUTES_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index f832b04c21..4135c67a13 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageLink; @@ -497,7 +498,7 @@ public class DefaultDataUpdateService implements DataUpdateService { md.getNodes().add(ruleNode); md.setFirstNodeIndex(newIdx); - md.addConnectionInfo(newIdx, oldIdx, "Success"); + md.addConnectionInfo(newIdx, oldIdx, TbNodeConnectionType.SUCCESS); ruleChainService.saveRuleChainMetaData(tenant.getId(), md, Function.identity()); } } catch (Exception e) { diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java index 6c2e5e9c5e..c448c4220f 100644 --- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.event.Event; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChain; @@ -159,7 +160,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2)); metaData.setFirstNodeIndex(0); - metaData.addConnectionInfo(0, 1, "Success"); + metaData.addConnectionInfo(0, 1, TbNodeConnectionType.SUCCESS); metaData = saveRuleChainMetaData(metaData); Assert.assertNotNull(metaData); @@ -265,7 +266,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule NodeConnectionInfo connection = new NodeConnectionInfo(); connection.setFromIndex(0); connection.setToIndex(1); - connection.setType("Success"); + connection.setType(TbNodeConnectionType.SUCCESS); rootMetaData.setConnections(Collections.singletonList(connection)); rootMetaData = saveRuleChainMetaData(rootMetaData); Assert.assertNotNull(rootMetaData); diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java index dc3cc84c72..c1f34217ac 100644 --- a/application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java @@ -24,9 +24,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; @@ -45,6 +47,8 @@ import java.util.UUID; @RunWith(MockitoJUnitRunner.class) public class RuleChainMsgConstructorTest { + private static final String RPC_CONNECTION_TYPE = "RPC"; + private RuleChainMsgConstructor constructor; private TenantId tenantId; @@ -99,19 +103,19 @@ public class RuleChainMsgConstructorTest { Assert.assertEquals("Connections count incorrect!", 13, ruleChainMetadataUpdateMsg.getConnectionsCount()); Assert.assertEquals("Rule chain connections count incorrect!", 0, ruleChainMetadataUpdateMsg.getRuleChainConnectionsCount()); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 6, "Success"), ruleChainMetadataUpdateMsg.getConnections(0)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 10, "Success"), ruleChainMetadataUpdateMsg.getConnections(1)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 0, "Success"), ruleChainMetadataUpdateMsg.getConnections(2)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 11, "Success"), ruleChainMetadataUpdateMsg.getConnections(3)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 11, "Success"), ruleChainMetadataUpdateMsg.getConnections(4)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 11, "Attributes Updated"), ruleChainMetadataUpdateMsg.getConnections(5)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 7, "RPC Request from Device"), ruleChainMetadataUpdateMsg.getConnections(6)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 4, "Post telemetry"), ruleChainMetadataUpdateMsg.getConnections(7)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 5, "Post attributes"), ruleChainMetadataUpdateMsg.getConnections(8)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 8, "Other"), ruleChainMetadataUpdateMsg.getConnections(9)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 9, "RPC Request to Device"), ruleChainMetadataUpdateMsg.getConnections(10)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(7, 11, "Success"), ruleChainMetadataUpdateMsg.getConnections(11)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(10, 9, "RPC"), ruleChainMetadataUpdateMsg.getConnections(12)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 6, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(0)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 10, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(1)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 0, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(2)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 11, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(3)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 11, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(4)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 11, TbMsgType.ATTRIBUTES_UPDATED.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(5)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 7, TbMsgType.TO_SERVER_RPC_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(6)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 4, TbMsgType.POST_TELEMETRY_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(7)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 5, TbMsgType.POST_ATTRIBUTES_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(8)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 8, TbNodeConnectionType.OTHER), ruleChainMetadataUpdateMsg.getConnections(9)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 9, TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(10)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(7, 11, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(11)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(10, 9, RPC_CONNECTION_TYPE), ruleChainMetadataUpdateMsg.getConnections(12)); } @Test @@ -130,20 +134,20 @@ public class RuleChainMsgConstructorTest { Assert.assertEquals("Connections count incorrect!", 10, ruleChainMetadataUpdateMsg.getConnectionsCount()); Assert.assertEquals("Rule chain connections count incorrect!", 1, ruleChainMetadataUpdateMsg.getRuleChainConnectionsCount()); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(2, 5, "Success"), ruleChainMetadataUpdateMsg.getConnections(0)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 9, "Success"), ruleChainMetadataUpdateMsg.getConnections(1)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 9, "Success"), ruleChainMetadataUpdateMsg.getConnections(2)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 9, "Attributes Updated"), ruleChainMetadataUpdateMsg.getConnections(3)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 6, "RPC Request from Device"), ruleChainMetadataUpdateMsg.getConnections(4)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 3, "Post telemetry"), ruleChainMetadataUpdateMsg.getConnections(5)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 4, "Post attributes"), ruleChainMetadataUpdateMsg.getConnections(6)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 7, "Other"), ruleChainMetadataUpdateMsg.getConnections(7)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 8, "RPC Request to Device"), ruleChainMetadataUpdateMsg.getConnections(8)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 9, "Success"), ruleChainMetadataUpdateMsg.getConnections(9)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(2, 5, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(0)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 9, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(1)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 9, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(2)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 9, TbMsgType.ATTRIBUTES_UPDATED.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(3)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 6, TbMsgType.TO_SERVER_RPC_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(4)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 3, TbMsgType.POST_TELEMETRY_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(5)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 4, TbMsgType.POST_ATTRIBUTES_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(6)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 7, TbNodeConnectionType.OTHER), ruleChainMetadataUpdateMsg.getConnections(7)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 8, TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(8)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 9, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(9)); RuleChainConnectionInfoProto ruleChainConnection = ruleChainMetadataUpdateMsg.getRuleChainConnections(0); Assert.assertEquals("From index incorrect!", 2, ruleChainConnection.getFromIndex()); - Assert.assertEquals("Type index incorrect!", "Success", ruleChainConnection.getType()); + Assert.assertEquals("Type index incorrect!", TbNodeConnectionType.SUCCESS, ruleChainConnection.getType()); Assert.assertEquals("Additional info incorrect!", "{\"description\":\"\",\"layoutX\":477,\"layoutY\":560,\"ruleChainNodeId\":\"rule-chain-node-UNDEFINED\"}", ruleChainConnection.getAdditionalInfo()); @@ -172,20 +176,20 @@ public class RuleChainMsgConstructorTest { Assert.assertEquals("Connections count incorrect!", 10, ruleChainMetadataUpdateMsg.getConnectionsCount()); Assert.assertEquals("Rule chain connections count incorrect!", 1, ruleChainMetadataUpdateMsg.getRuleChainConnectionsCount()); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 0, "Success"), ruleChainMetadataUpdateMsg.getConnections(0)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 0, "Attributes Updated"), ruleChainMetadataUpdateMsg.getConnections(1)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 3, "RPC Request from Device"), ruleChainMetadataUpdateMsg.getConnections(2)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 6, "Post telemetry"), ruleChainMetadataUpdateMsg.getConnections(3)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 5, "Post attributes"), ruleChainMetadataUpdateMsg.getConnections(4)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 2, "Other"), ruleChainMetadataUpdateMsg.getConnections(5)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 1, "RPC Request to Device"), ruleChainMetadataUpdateMsg.getConnections(6)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 0, "Success"), ruleChainMetadataUpdateMsg.getConnections(7)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 0, "Success"), ruleChainMetadataUpdateMsg.getConnections(8)); - compareNodeConnectionInfoAndProto(createNodeConnectionInfo(7, 4, "Success"), ruleChainMetadataUpdateMsg.getConnections(9)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(3, 0, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(0)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 0, TbMsgType.ATTRIBUTES_UPDATED.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(1)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 3, TbMsgType.TO_SERVER_RPC_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(2)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 6, TbMsgType.POST_TELEMETRY_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(3)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 5, TbMsgType.POST_ATTRIBUTES_REQUEST.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(4)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 2, TbNodeConnectionType.OTHER), ruleChainMetadataUpdateMsg.getConnections(5)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(4, 1, TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.getRuleNodeConnection()), ruleChainMetadataUpdateMsg.getConnections(6)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(5, 0, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(7)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(6, 0, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(8)); + compareNodeConnectionInfoAndProto(createNodeConnectionInfo(7, 4, TbNodeConnectionType.SUCCESS), ruleChainMetadataUpdateMsg.getConnections(9)); RuleChainConnectionInfoProto ruleChainConnection = ruleChainMetadataUpdateMsg.getRuleChainConnections(0); Assert.assertEquals("From index incorrect!", 7, ruleChainConnection.getFromIndex()); - Assert.assertEquals("Type index incorrect!", "Success", ruleChainConnection.getType()); + Assert.assertEquals("Type index incorrect!", TbNodeConnectionType.SUCCESS, ruleChainConnection.getType()); Assert.assertEquals("Additional info incorrect!", "{\"description\":\"\",\"layoutX\":477,\"layoutY\":560,\"ruleChainNodeId\":\"rule-chain-node-UNDEFINED\"}", ruleChainConnection.getAdditionalInfo()); @@ -225,19 +229,19 @@ public class RuleChainMsgConstructorTest { private List createConnections() { List result = new ArrayList<>(); - result.add(createNodeConnectionInfo(3, 6, "Success")); - result.add(createNodeConnectionInfo(3, 10, "Success")); - result.add(createNodeConnectionInfo(3, 0, "Success")); - result.add(createNodeConnectionInfo(4, 11, "Success")); - result.add(createNodeConnectionInfo(5, 11, "Success")); - result.add(createNodeConnectionInfo(6, 11, "Attributes Updated")); - result.add(createNodeConnectionInfo(6, 7, "RPC Request from Device")); - result.add(createNodeConnectionInfo(6, 4, "Post telemetry")); - result.add(createNodeConnectionInfo(6, 5, "Post attributes")); - result.add(createNodeConnectionInfo(6, 8, "Other")); - result.add(createNodeConnectionInfo(6, 9, "RPC Request to Device")); - result.add(createNodeConnectionInfo(7, 11, "Success")); - result.add(createNodeConnectionInfo(10, 9, "RPC")); + result.add(createNodeConnectionInfo(3, 6, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(3, 10, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(3, 0, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(4, 11, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(5, 11, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(6, 11, TbMsgType.ATTRIBUTES_UPDATED.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(6, 7, TbMsgType.TO_SERVER_RPC_REQUEST.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(6, 4, TbMsgType.POST_TELEMETRY_REQUEST.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(6, 5, TbMsgType.POST_ATTRIBUTES_REQUEST.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(6, 8, TbNodeConnectionType.OTHER)); + result.add(createNodeConnectionInfo(6, 9, TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(7, 11, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(10, 9, RPC_CONNECTION_TYPE)); return result; } @@ -280,19 +284,19 @@ public class RuleChainMsgConstructorTest { private List createConnectionsInDifferentOrder() { List result = new ArrayList<>(); - result.add(createNodeConnectionInfo(0, 2, "RPC")); - result.add(createNodeConnectionInfo(4, 1, "Success")); - result.add(createNodeConnectionInfo(5, 1, "Attributes Updated")); - result.add(createNodeConnectionInfo(5, 4, "RPC Request from Device")); - result.add(createNodeConnectionInfo(5, 7, "Post telemetry")); - result.add(createNodeConnectionInfo(5, 6, "Post attributes")); - result.add(createNodeConnectionInfo(5, 3, "Other")); - result.add(createNodeConnectionInfo(5, 2, "RPC Request to Device")); - result.add(createNodeConnectionInfo(6, 1, "Success")); - result.add(createNodeConnectionInfo(7, 1, "Success")); - result.add(createNodeConnectionInfo(8, 11, "Success")); - result.add(createNodeConnectionInfo(8, 5, "Success")); - result.add(createNodeConnectionInfo(8, 0, "Success")); + result.add(createNodeConnectionInfo(0, 2, RPC_CONNECTION_TYPE)); + result.add(createNodeConnectionInfo(4, 1, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(5, 1, TbMsgType.ATTRIBUTES_UPDATED.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(5, 4, TbMsgType.TO_SERVER_RPC_REQUEST.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(5, 7, TbMsgType.POST_TELEMETRY_REQUEST.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(5, 6, TbMsgType.POST_ATTRIBUTES_REQUEST.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(5, 3, TbNodeConnectionType.OTHER)); + result.add(createNodeConnectionInfo(5, 2, TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.getRuleNodeConnection())); + result.add(createNodeConnectionInfo(6, 1, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(7, 1, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(8, 11, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(8, 5, TbNodeConnectionType.SUCCESS)); + result.add(createNodeConnectionInfo(8, 0, TbNodeConnectionType.SUCCESS)); return result; } diff --git a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java index 4373a4c108..8d7de23303 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java @@ -33,6 +33,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @DaoSqlTest @TestPropertySource(properties = { @@ -121,7 +122,7 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest { } private String invokeScript(UUID scriptId, String msg) throws ExecutionException, InterruptedException { - return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", "POST_TELEMETRY_REQUEST").get().toString(); + return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.getRuleNodeConnection()).get().toString(); } } diff --git a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java index ca5dbef5f1..2895c97c90 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java @@ -42,6 +42,7 @@ import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @DaoSqlTest @TestPropertySource(properties = { @@ -216,7 +217,7 @@ class TbelInvokeServiceTest extends AbstractControllerTest { private String invokeScript(UUID scriptId, String str) throws ExecutionException, InterruptedException { var msg = JacksonUtil.fromString(str, Map.class); - return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", "POST_TELEMETRY_REQUEST").get().toString(); + return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.getRuleNodeConnection()).get().toString(); } } diff --git a/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java b/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java index e70a888194..6c060feb14 100644 --- a/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java @@ -37,7 +37,6 @@ import org.thingsboard.server.common.data.security.Authority; 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.session.SessionMsgType; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -51,6 +50,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @DaoSqlTest public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest { @@ -133,7 +133,7 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest void saveLatestTsForAssetAndDevice(List devices, Asset asset, int idx) throws ExecutionException, InterruptedException, TimeoutException { for (Device device : devices) { - TbMsg tbMsg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), + TbMsg tbMsg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), device.getId(), getTbMsgMetadata(device.getName(), ts.get(idx)), TbMsgDataType.JSON, diff --git a/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java b/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java index 39b61c6c47..b9b023e9e9 100644 --- a/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java @@ -52,6 +52,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -343,7 +344,7 @@ public abstract class BaseExportImportServiceTest extends AbstractControllerTest metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2)); metaData.setFirstNodeIndex(0); - metaData.addConnectionInfo(0, 1, "Success"); + metaData.addConnectionInfo(0, 1, TbNodeConnectionType.SUCCESS); ruleChainService.saveRuleChainMetaData(tenantId, metaData, Function.identity()); return ruleChainService.findRuleChainById(tenantId, ruleChain.getId()); @@ -381,7 +382,7 @@ public abstract class BaseExportImportServiceTest extends AbstractControllerTest metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2)); metaData.setFirstNodeIndex(0); - metaData.addConnectionInfo(0, 1, "Success"); + metaData.addConnectionInfo(0, 1, TbNodeConnectionType.SUCCESS); ruleChainService.saveRuleChainMetaData(tenantId, metaData, Function.identity()); return ruleChainService.findRuleChainById(tenantId, ruleChain.getId()); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java index 645b8cc9fd..a974d8adbd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java @@ -68,27 +68,25 @@ public enum TbMsgType { PROVISION_SUCCESS(null), PROVISION_FAILURE(null); - public static final String OTHER = "Other"; - public static final List NODE_CONNECTIONS = EnumSet.allOf(TbMsgType.class).stream() - .map(TbMsgType::getNodeConnection).filter(Objects::nonNull).collect(Collectors.toUnmodifiableList()); + .map(TbMsgType::getRuleNodeConnection).filter(Objects::nonNull).collect(Collectors.toUnmodifiableList()); @Getter - private final String nodeConnection; + private final String ruleNodeConnection; - TbMsgType(String nodeConnection) { - this.nodeConnection = nodeConnection; + TbMsgType(String ruleNodeConnection) { + this.ruleNodeConnection = ruleNodeConnection; } - public static String getNodeConnection(String msgType) { + public static String getRuleNodeConnection(String msgType) { if (msgType == null) { - return OTHER; + return TbNodeConnectionType.OTHER; } else { return Arrays.stream(TbMsgType.values()) .filter(type -> type.name().equals(msgType)) .findFirst() - .map(TbMsgType::getNodeConnection) - .orElse(OTHER); + .map(TbMsgType::getRuleNodeConnection) + .orElse(TbNodeConnectionType.OTHER); } } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConnectionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbNodeConnectionType.java similarity index 90% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConnectionType.java rename to common/data/src/main/java/org/thingsboard/server/common/data/msg/TbNodeConnectionType.java index e19ac3f6af..584393bf53 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConnectionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbNodeConnectionType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api; +package org.thingsboard.server.common.data.msg; /** * Created by ashvayka on 19.01.18. @@ -26,4 +26,6 @@ public final class TbNodeConnectionType { public static final String TRUE = "True"; public static final String FALSE = "False"; + public static final String OTHER = "Other"; + } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java deleted file mode 100644 index dfcf178473..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java +++ /dev/null @@ -1,48 +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.common.msg.session; - -public enum SessionMsgType { - GET_ATTRIBUTES_REQUEST(true), POST_ATTRIBUTES_REQUEST(true), GET_ATTRIBUTES_RESPONSE, - SUBSCRIBE_ATTRIBUTES_REQUEST, UNSUBSCRIBE_ATTRIBUTES_REQUEST, ATTRIBUTES_UPDATE_NOTIFICATION, - - POST_TELEMETRY_REQUEST(true), STATUS_CODE_RESPONSE, - - SUBSCRIBE_RPC_COMMANDS_REQUEST, UNSUBSCRIBE_RPC_COMMANDS_REQUEST, - TO_DEVICE_RPC_REQUEST, TO_DEVICE_RPC_RESPONSE, TO_DEVICE_RPC_RESPONSE_ACK, - - TO_SERVER_RPC_REQUEST(true), TO_SERVER_RPC_RESPONSE, - - RULE_ENGINE_ERROR, - - SESSION_OPEN, SESSION_CLOSE, - - CLAIM_REQUEST(); - - private final boolean requiresRulesProcessing; - - SessionMsgType() { - this(false); - } - - SessionMsgType(boolean requiresRulesProcessing) { - this.requiresRulesProcessing = requiresRulesProcessing; - } - - public boolean requiresRulesProcessing() { - return requiresRulesProcessing; - } -} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapSessionMsgType.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapSessionMsgType.java new file mode 100644 index 0000000000..fcf33ea037 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapSessionMsgType.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.transport.coap; + +public enum CoapSessionMsgType { + + GET_ATTRIBUTES_REQUEST, + POST_ATTRIBUTES_REQUEST, + SUBSCRIBE_ATTRIBUTES_REQUEST, + UNSUBSCRIBE_ATTRIBUTES_REQUEST, + POST_TELEMETRY_REQUEST, + SUBSCRIBE_RPC_COMMANDS_REQUEST, + UNSUBSCRIBE_RPC_COMMANDS_REQUEST, + TO_DEVICE_RPC_RESPONSE, + TO_SERVER_RPC_REQUEST, + CLAIM_REQUEST; + +} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 7dde25bfd0..d6958137c7 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -33,7 +33,6 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; @@ -120,7 +119,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } else if (exchange.getRequestOptions().hasObserve()) { processExchangeGetRequest(exchange, featureType.get()); } else if (featureType.get() == FeatureType.ATTRIBUTES) { - processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); + processRequest(exchange, CoapSessionMsgType.GET_ATTRIBUTES_REQUEST); } else { log.trace("Invalid feature type parameter"); exchange.respond(CoAP.ResponseCode.BAD_REQUEST); @@ -129,13 +128,13 @@ public class CoapTransportResource extends AbstractCoapTransportResource { private void processExchangeGetRequest(CoapExchange exchange, FeatureType featureType) { boolean unsubscribe = exchange.getRequestOptions().getObserve() == 1; - SessionMsgType sessionMsgType; + CoapSessionMsgType coapSessionMsgType; if (featureType == FeatureType.RPC) { - sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; + coapSessionMsgType = unsubscribe ? CoapSessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : CoapSessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; } else { - sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; + coapSessionMsgType = unsubscribe ? CoapSessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : CoapSessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; } - processRequest(exchange, sessionMsgType); + processRequest(exchange, coapSessionMsgType); } @Override @@ -147,21 +146,21 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } else { switch (featureType.get()) { case ATTRIBUTES: - processRequest(exchange, SessionMsgType.POST_ATTRIBUTES_REQUEST); + processRequest(exchange, CoapSessionMsgType.POST_ATTRIBUTES_REQUEST); break; case TELEMETRY: - processRequest(exchange, SessionMsgType.POST_TELEMETRY_REQUEST); + processRequest(exchange, CoapSessionMsgType.POST_TELEMETRY_REQUEST); break; case RPC: Optional requestId = getRequestId(exchange.advanced().getRequest()); if (requestId.isPresent()) { - processRequest(exchange, SessionMsgType.TO_DEVICE_RPC_RESPONSE); + processRequest(exchange, CoapSessionMsgType.TO_DEVICE_RPC_RESPONSE); } else { - processRequest(exchange, SessionMsgType.TO_SERVER_RPC_REQUEST); + processRequest(exchange, CoapSessionMsgType.TO_SERVER_RPC_REQUEST); } break; case CLAIM: - processRequest(exchange, SessionMsgType.CLAIM_REQUEST); + processRequest(exchange, CoapSessionMsgType.CLAIM_REQUEST); break; case PROVISION: processProvision(exchange); @@ -195,7 +194,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } - private void processRequest(CoapExchange exchange, SessionMsgType type) { + private void processRequest(CoapExchange exchange, CoapSessionMsgType type) { log.trace("Processing {}", exchange.advanced().getRequest()); deferAccept(exchange); Exchange advanced = exchange.advanced(); @@ -218,7 +217,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } - private void processAccessTokenRequest(CoapExchange exchange, SessionMsgType type, Request request) { + private void processAccessTokenRequest(CoapExchange exchange, CoapSessionMsgType type, Request request) { Optional credentials = decodeCredentials(request); if (credentials.isEmpty()) { exchange.respond(CoAP.ResponseCode.UNAUTHORIZED); @@ -228,7 +227,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { new CoapDeviceAuthCallback(exchange, (deviceCredentials, deviceProfile) -> processRequest(exchange, type, request, deviceCredentials, deviceProfile))); } - private void processRequest(CoapExchange exchange, SessionMsgType type, Request request, ValidateDeviceCredentialsResponse deviceCredentials, DeviceProfile deviceProfile) { + private void processRequest(CoapExchange exchange, CoapSessionMsgType type, Request request, ValidateDeviceCredentialsResponse deviceCredentials, DeviceProfile deviceProfile) { TbCoapClientState clientState = null; try { clientState = clients.getOrCreateClient(type, deviceCredentials, deviceProfile); diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/CoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/CoapClientContext.java index 36ef8472de..a8531a7fba 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/CoapClientContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/CoapClientContext.java @@ -18,7 +18,7 @@ package org.thingsboard.server.transport.coap.client; import org.eclipse.californium.core.observe.ObserveRelation; import org.eclipse.californium.core.server.resources.CoapExchange; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.transport.coap.CoapSessionMsgType; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; @@ -33,7 +33,7 @@ public interface CoapClientContext { AtomicInteger getNotificationCounterByToken(String token); - TbCoapClientState getOrCreateClient(SessionMsgType type, ValidateDeviceCredentialsResponse deviceCredentials, DeviceProfile deviceProfile) throws AdaptorException; + TbCoapClientState getOrCreateClient(CoapSessionMsgType type, ValidateDeviceCredentialsResponse deviceCredentials, DeviceProfile deviceProfile) throws AdaptorException; TransportProtos.SessionInfoProto getNewSyncSession(TbCoapClientState clientState); diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java index f70967b72d..26effe7058 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java @@ -45,7 +45,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.msg.session.FeatureType; -import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.transport.coap.CoapSessionMsgType; import org.thingsboard.server.common.transport.DeviceDeletedEvent; import org.thingsboard.server.common.transport.DeviceProfileUpdatedEvent; import org.thingsboard.server.common.transport.DeviceUpdatedEvent; @@ -388,7 +388,7 @@ public class DefaultCoapClientContext implements CoapClientContext { } @Override - public TbCoapClientState getOrCreateClient(SessionMsgType type, ValidateDeviceCredentialsResponse deviceCredentials, DeviceProfile deviceProfile) throws AdaptorException { + public TbCoapClientState getOrCreateClient(CoapSessionMsgType type, ValidateDeviceCredentialsResponse deviceCredentials, DeviceProfile deviceProfile) throws AdaptorException { DeviceId deviceId = deviceCredentials.getDeviceInfo().getDeviceId(); TbCoapClientState state = getClientState(deviceId); state.lock(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 3b827edbae..6500467af7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -49,14 +49,14 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.limit.LimitedApi; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; -import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.stats.MessagesStats; import org.thingsboard.server.common.stats.StatsFactory; @@ -583,7 +583,7 @@ public class DefaultTransportService implements TransportService { metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("ts", tsKv.getTs() + ""); JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); - sendToRuleEngine(tenantId, deviceId, customerId, sessionInfo, json, metaData, SessionMsgType.POST_TELEMETRY_REQUEST, packCallback); + sendToRuleEngine(tenantId, deviceId, customerId, sessionInfo, json, metaData, TbMsgType.POST_TELEMETRY_REQUEST, packCallback); } } } @@ -603,7 +603,7 @@ public class DefaultTransportService implements TransportService { } metaData.putValue(DataConstants.NOTIFY_DEVICE_METADATA_KEY, "false"); CustomerId customerId = getCustomerId(sessionInfo); - sendToRuleEngine(tenantId, deviceId, customerId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, + sendToRuleEngine(tenantId, deviceId, customerId, sessionInfo, json, metaData, TbMsgType.POST_ATTRIBUTES_REQUEST, new TransportTbQueueCallback(new ApiStatsProxyCallback<>(tenantId, customerId, msg.getKvList().size(), callback))); } } @@ -723,7 +723,7 @@ public class DefaultTransportService implements TransportService { metaData.putValue("serviceId", serviceInfoProvider.getServiceId()); metaData.putValue("sessionId", sessionId.toString()); sendToRuleEngine(tenantId, deviceId, getCustomerId(sessionInfo), sessionInfo, json, metaData, - SessionMsgType.TO_SERVER_RPC_REQUEST, new TransportTbQueueCallback(callback)); + TbMsgType.TO_SERVER_RPC_REQUEST, new TransportTbQueueCallback(callback)); String requestId = sessionId + "-" + msg.getRequestId(); toServerRpcPendingMap.put(requestId, new RpcRequestMetadata(sessionId, msg.getRequestId())); scheduler.schedule(() -> processTimeout(requestId), clientSideRpcTimeout, TimeUnit.MILLISECONDS); @@ -1136,7 +1136,7 @@ public class DefaultTransportService implements TransportService { } private void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, CustomerId customerId, TransportProtos.SessionInfoProto sessionInfo, JsonObject json, - TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) { + TbMsgMetaData metaData, TbMsgType tbMsgType, TbQueueCallback callback) { DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); RuleChainId ruleChainId; @@ -1151,7 +1151,7 @@ public class DefaultTransportService implements TransportService { queueName = deviceProfile.getDefaultQueueName(); } - TbMsg tbMsg = TbMsg.newMsg(queueName, sessionMsgType.name(), deviceId, customerId, metaData, gson.toJson(json), ruleChainId, null); + TbMsg tbMsg = TbMsg.newMsg(queueName, tbMsgType.name(), deviceId, customerId, metaData, gson.toJson(json), ruleChainId, null); sendToRuleEngine(tenantId, tbMsg, callback); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java index f35e7e608f..cf2ff1e7a0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.event.RuleChainDebugEventFilter; import org.thingsboard.server.common.data.event.RuleNodeDebugEventFilter; import org.thingsboard.server.common.data.event.StatisticsEventFilter; import org.thingsboard.server.common.data.id.EventId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.stats.StatsFactory; @@ -280,7 +281,7 @@ public class JpaBaseEventDao implements EventDao { private PageData findEventByFilter(UUID tenantId, UUID entityId, LifeCycleEventFilter eventFilter, TimePageLink pageLink) { boolean statusFilterEnabled = !StringUtils.isEmpty(eventFilter.getStatus()); - boolean statusFilter = statusFilterEnabled && eventFilter.getStatus().equalsIgnoreCase("Success"); + boolean statusFilter = statusFilterEnabled && eventFilter.getStatus().equalsIgnoreCase(TbNodeConnectionType.SUCCESS); return DaoUtil.toPageData( lcEventRepository.findEvents( tenantId, @@ -359,7 +360,7 @@ public class JpaBaseEventDao implements EventDao { private void removeEventsByFilter(UUID tenantId, UUID entityId, LifeCycleEventFilter eventFilter, Long startTime, Long endTime) { boolean statusFilterEnabled = !StringUtils.isEmpty(eventFilter.getStatus()); - boolean statusFilter = statusFilterEnabled && eventFilter.getStatus().equalsIgnoreCase("Success"); + boolean statusFilter = statusFilterEnabled && eventFilter.getStatus().equalsIgnoreCase(TbNodeConnectionType.SUCCESS); lcEventRepository.removeEvents( tenantId, entityId, diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java index 7ee61bac1d..7037f2f3fe 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java @@ -15,6 +15,7 @@ */ package org.thingsboard.rule.engine.api; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentClusteringMode; import org.thingsboard.server.common.data.plugin.ComponentScope; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -47,7 +48,7 @@ public @interface RuleNode { ComponentScope scope() default ComponentScope.TENANT; - String[] relationTypes() default {"Success", "Failure"}; + String[] relationTypes() default {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE}; String[] uiResources() default {}; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index 5f48f54436..bc27154ad8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -24,7 +24,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.script.ScriptLanguage; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java index c74bbe0008..ea53138d10 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java @@ -57,8 +57,8 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import static org.thingsboard.common.util.DonAsynchron.withCallback; -import static org.thingsboard.rule.engine.api.TbNodeConnectionType.FAILURE; -import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.FAILURE; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; @Slf4j public abstract class TbAbstractRelationActionNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 61004bfa11..4f69225274 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -49,7 +49,7 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETE import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; -import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java index 58c42a1109..d9a99c3229 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java @@ -27,13 +27,13 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.msg.session.SessionMsgType; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( @@ -77,7 +77,7 @@ public class TbMsgCountNode implements TbNode { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("delta", Long.toString(System.currentTimeMillis() - lastScheduledTs + delay)); - TbMsg tbMsg = TbMsg.newMsg(msg.getQueueName(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), ctx.getTenantId(), msg.getCustomerId(), metaData, gson.toJson(telemetryJson)); + TbMsg tbMsg = TbMsg.newMsg(msg.getQueueName(), POST_TELEMETRY_REQUEST.name(), ctx.getTenantId(), msg.getCustomerId(), metaData, gson.toJson(telemetryJson)); ctx.enqueueForTellNext(tbMsg, SUCCESS); scheduleTickMsg(ctx, tbMsg); } else { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 6bd8f52285..75b63c59e9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -41,7 +41,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static org.thingsboard.common.util.DonAsynchron.withCallback; -import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index af119d263f..5654b4095a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -24,7 +24,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.plugin.ComponentType; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index 17224c18b8..00ba3acc50 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -32,7 +32,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; -import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java index 869dc7ca07..d70dd75040 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java @@ -31,11 +31,11 @@ import org.thingsboard.server.common.data.plugin.ComponentType; type = ComponentType.FILTER, name = "asset profile switch", customRelations = true, - relationTypes = {}, + relationTypes = {"default"}, configClazz = EmptyNodeConfiguration.class, nodeDescription = "Route incoming messages based on the name of the asset profile", - nodeDetails = "Route incoming messages based on the name of the asset profile. The asset profile name is case-sensitive

" + - "Output connection types: Profile name of message originator or Failure", + nodeDetails = "Route incoming messages based on the name of the asset profile. The asset profile name is case-sensitive.

" + + "Output connections: Message originator profile name or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbAssetTypeSwitchNode extends TbAbstractTypeSwitchNode { @@ -43,7 +43,8 @@ public class TbAssetTypeSwitchNode extends TbAbstractTypeSwitchNode { @Override protected String getRelationType(TbContext ctx, EntityId originator) throws TbNodeException { if (!EntityType.ASSET.equals(originator.getEntityType())) { - throw new TbNodeException("Unsupported originator type: " + originator.getEntityType() + "! Only 'ASSET' type is allowed."); + throw new TbNodeException("Unsupported originator type: " + originator.getEntityType().getNormalName() + "!" + + " Only " + EntityType.ASSET.getNormalName() + " type is allowed."); } AssetProfile assetProfile = ctx.getAssetProfileCache().get(ctx.getTenantId(), (AssetId) originator); if (assetProfile == null) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java index f76366e622..ca58e47554 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java @@ -25,9 +25,9 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -41,7 +41,7 @@ import javax.annotation.Nullable; relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Checks alarm status.", nodeDetails = "Checks the alarm status to match one of the specified statuses.

" + - "Output connection types: True, False, Failure.", + "Output connections: True, False, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckAlarmStatusConfig") public class TbCheckAlarmStatusNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java index c461b258a8..a5ee3b969b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNode.java @@ -22,7 +22,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -39,7 +39,7 @@ import java.util.Map; nodeDescription = "Checks the presence of the specified fields in the message and/or metadata.", nodeDetails = "By default, the rule node checks that all specified fields are present. " + "Uncheck the 'Check that all selected fields are present' if the presence of at least one field is sufficient.

" + - "Output connection types: True, False, Failure", + "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckMessageConfig") public class TbCheckMessageNode implements TbNode { @@ -60,11 +60,10 @@ public class TbCheckMessageNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { try { - if (config.isCheckAllKeys()) { - ctx.tellNext(msg, allKeysData(msg) && allKeysMetadata(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); - } else { - ctx.tellNext(msg, atLeastOneData(msg) || atLeastOneMetadata(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE); - } + String relationType = config.isCheckAllKeys() ? + allKeysData(msg) && allKeysMetadata(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE : + atLeastOneData(msg) || atLeastOneMetadata(msg) ? TbNodeConnectionType.TRUE : TbNodeConnectionType.FALSE; + ctx.tellNext(msg, relationType); } catch (Exception e) { ctx.tellFailure(msg, e); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeConfiguration.java index f145be3fd2..25a6efa417 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeConfiguration.java @@ -22,7 +22,7 @@ import java.util.Collections; import java.util.List; @Data -public class TbCheckMessageNodeConfiguration implements NodeConfiguration { +public class TbCheckMessageNodeConfiguration implements NodeConfiguration { private List messageNames; private List metadataNames; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index 43bec5a3c1..3dfa6cb233 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -17,14 +17,13 @@ package org.thingsboard.rule.engine.filter; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -51,7 +50,7 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; nodeDetails = "If 'check relation to specific entity' is selected, you should specify a related entity. " + "Otherwise, the rule node checks the presence of a relation to any entity. " + "In both cases, relation lookup is based on configured direction and type.

" + - "Output connection types: True, False, Failure", + "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckRelationConfig") public class TbCheckRelationNode implements TbNode { @@ -91,13 +90,10 @@ public class TbCheckRelationNode implements TbNode { } private ListenableFuture processList(TbContext ctx, TbMsg msg) { - if (EntitySearchDirection.FROM.name().equals(config.getDirection())) { - return Futures.transformAsync(ctx.getRelationService() - .findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON), this::isEmptyList, MoreExecutors.directExecutor()); - } else { - return Futures.transformAsync(ctx.getRelationService() - .findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON), this::isEmptyList, MoreExecutors.directExecutor()); - } + ListenableFuture> relationListFuture = EntitySearchDirection.FROM.name().equals(config.getDirection()) ? ctx.getRelationService() + .findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON) : ctx.getRelationService() + .findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON); + return Futures.transformAsync(relationListFuture, this::isEmptyList, ctx.getDbCallbackExecutor()); } private ListenableFuture isEmptyList(List entityRelations) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java index aeda247583..ad5574ec8c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.filter; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; /** @@ -35,7 +36,7 @@ public class TbCheckRelationNodeConfiguration implements NodeConfigurationFailure", + "Output connections: Message originator profile name or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbDeviceTypeSwitchNode extends TbAbstractTypeSwitchNode { @@ -43,7 +43,8 @@ public class TbDeviceTypeSwitchNode extends TbAbstractTypeSwitchNode { @Override protected String getRelationType(TbContext ctx, EntityId originator) throws TbNodeException { if (!EntityType.DEVICE.equals(originator.getEntityType())) { - throw new TbNodeException("Unsupported originator type: " + originator.getEntityType() + "! Only 'DEVICE' type is allowed."); + throw new TbNodeException("Unsupported originator type: " + originator.getEntityType().getNormalName() + + "! Only " + EntityType.DEVICE.getNormalName() + " type is allowed."); } DeviceProfile deviceProfile = ctx.getDeviceProfileCache().get(ctx.getTenantId(), (DeviceId) originator); if (deviceProfile == null) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index 1b8461b44b..bcfaf78a65 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -22,7 +22,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.script.ScriptLanguage; @@ -43,7 +43,7 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; "Message payload can be accessed via msg property. For example msg.temperature < 10;
" + "Message metadata can be accessed via metadata property. For example metadata.customerName === 'John';
" + "Message type can be accessed via msgType property.

" + - "Output connection types: True, False, Failure", + "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeScriptConfig" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index 8c0f058ed3..f55a172c02 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -45,7 +45,7 @@ import java.util.Set; "Message payload can be accessed via msg property. For example msg.temperature < 10;
" + "Message metadata can be accessed via metadata property. For example metadata.customerName === 'John';
" + "Message type can be accessed via msgType property.

" + - "Output connection types: Custom connection(s) defined by switch node or Failure", + "Output connections: Custom connection(s) defined by switch node or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeSwitchConfig") public class TbJsSwitchNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java index 5074991cb1..ca8ba9d561 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java @@ -21,7 +21,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -32,12 +32,12 @@ import org.thingsboard.server.common.msg.TbMsg; @Slf4j @RuleNode( type = ComponentType.FILTER, - name = "message type", + name = "message type filter", configClazz = TbMsgTypeFilterNodeConfiguration.class, relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Filter incoming messages by Message Type", nodeDetails = "If incoming message type is expected - send Message via True chain, otherwise False chain is used.

" + - "Output connection types: True, False, Failure", + "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeMessageTypeConfig") public class TbMsgTypeFilterNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java index fb326a6d50..1c0b080653 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java @@ -17,11 +17,14 @@ package org.thingsboard.rule.engine.filter; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; -import org.thingsboard.server.common.msg.session.SessionMsgType; import java.util.Arrays; import java.util.List; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; +import static org.thingsboard.server.common.data.msg.TbMsgType.TO_SERVER_RPC_REQUEST; + /** * Created by ashvayka on 19.01.18. */ @@ -32,11 +35,11 @@ public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration etc. via corresponding chain, otherwise Other chain is used.", + nodeDetails = "Sends messages with message types \"Post attributes\", \"Post telemetry\", \"RPC Request\"" + + " etc. via corresponding chain, otherwise Other chain is used.

" + + "Output connections: Message type connection, Other - if message type is custom or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbMsgTypeSwitchNode implements TbNode { @@ -48,7 +50,7 @@ public class TbMsgTypeSwitchNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - ctx.tellNext(msg, TbMsgType.getNodeConnection(msg.getType())); + ctx.tellNext(msg, TbMsgType.getRuleNodeConnection(msg.getType())); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java index 8c32d8a1c9..183721e72a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java @@ -21,7 +21,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -30,12 +30,12 @@ import org.thingsboard.server.common.msg.TbMsg; @Slf4j @RuleNode( type = ComponentType.FILTER, - name = "entity type", + name = "entity type filter", configClazz = TbOriginatorTypeFilterNodeConfiguration.class, relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Filter incoming messages by the type of message originator entity", nodeDetails = "Checks that the entity type of the incoming message originator matches one of the values specified in the filter.

" + - "Output connection types: True, False, Failure", + "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeOriginatorTypeConfig") public class TbOriginatorTypeFilterNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java index 11365a2b1d..9e14c6a7ab 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; relationTypes = {}, // should always be empty. We add the relation types for this node in AnnotationComponentDiscoveryService. nodeDescription = "Route incoming messages by Message Originator Type", nodeDetails = "Routes messages to chain according to the entity type ('Device', 'Asset', etc.).

" + - "Output connection types: entityType of the message originator or Failure", + "Output connections: Message originator type or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbOriginatorTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java index e0ffc564db..a92cafefd3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java @@ -21,7 +21,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java index b4d217d799..20e7039be4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java @@ -19,7 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -59,7 +59,7 @@ import org.thingsboard.server.common.msg.TbMsg; "{\"latitude\": 48.198618758582384, \"longitude\": 24.65322245153503, \"radius\": 100.0, \"radiusUnit\": \"METER\" }" + "

" + "Available radius units: METER, KILOMETER, FOOT, MILE, NAUTICAL_MILE;

" + - "Output connection types: True, False, Failure", + "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeGpsGeofencingConfig") public class TbGpsGeofencingFilterNode extends AbstractGeofencingNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index 7afd61da2d..051e646b78 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -31,7 +31,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.exception.ThingsboardKafkaClientError; import org.thingsboard.server.common.data.plugin.ComponentType; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index d56b0d6890..ae661e48e4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -34,7 +34,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -import static org.thingsboard.rule.engine.api.TbNodeConnectionType.SUCCESS; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; @Slf4j diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index 1a2ba6d646..d4bdd320d1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -25,13 +25,14 @@ import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.dao.timeseries.TimeseriesService; import java.math.BigDecimal; @@ -45,7 +46,7 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; @Slf4j @RuleNode(type = ComponentType.ENRICHMENT, - name = "calculate delta", relationTypes = {"Success", "Failure", "Other"}, + name = "calculate delta", relationTypes = {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE, TbNodeConnectionType.OTHER}, configClazz = CalculateDeltaNodeConfiguration.class, nodeDescription = "Calculates delta and amount of time passed between previous timeseries key reading " + "and current value for this key from the incoming message", @@ -73,14 +74,14 @@ public class CalculateDeltaNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (!msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { - ctx.tellNext(msg, "Other"); + if (!msg.getType().equals(TbMsgType.POST_TELEMETRY_REQUEST.name())) { + ctx.tellNext(msg, TbNodeConnectionType.OTHER); return; } JsonNode json = JacksonUtil.toJsonNode(msg.getData()); String inputKey = config.getInputValueKey(); if (!json.has(inputKey)) { - ctx.tellNext(msg, "Other"); + ctx.tellNext(msg, TbNodeConnectionType.OTHER); return; } withCallback(getLastValue(msg.getOriginator()), diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index c7580fb7d3..641daa82ca 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -43,7 +43,7 @@ import org.springframework.web.util.UriComponentsBuilder; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.credentials.BasicCredentials; import org.thingsboard.rule.engine.credentials.ClientCredentials; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java index 936b192c85..5f9a2f63ed 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java @@ -27,7 +27,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 9cf9bfa1bd..5ddb1c701f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import java.util.ArrayList; @@ -36,6 +35,7 @@ import java.util.List; import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE; import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_METADATA_KEY; import static org.thingsboard.server.common.data.DataConstants.SCOPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; @Slf4j @RuleNode( @@ -65,7 +65,7 @@ public class TbMsgAttributesNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (!msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) { + if (!msg.getType().equals(POST_ATTRIBUTES_REQUEST.name())) { ctx.tellFailure(msg, new IllegalArgumentException("Unsupported msg type: " + msg.getType())); return; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java index dcc84813cd..706135eaa2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java @@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import java.util.ArrayList; @@ -39,6 +38,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; + @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -81,7 +82,7 @@ public class TbMsgTimeseriesNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (!msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { + if (!msg.getType().equals(POST_TELEMETRY_REQUEST.name())) { ctx.tellFailure(msg, new IllegalArgumentException("Unsupported msg type: " + msg.getType())); return; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java index d96060ffc2..72570d4e2a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java @@ -22,7 +22,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.TbMsgCallback; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java index 9c86891bc0..1ee7ab7b03 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java @@ -25,7 +25,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/TestDbCallbackExecutor.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/TestDbCallbackExecutor.java new file mode 100644 index 0000000000..ed9053e2e2 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/TestDbCallbackExecutor.java @@ -0,0 +1,40 @@ +/** + * 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.rule.engine; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.common.util.ListeningExecutor; + +import java.util.concurrent.Callable; + +public class TestDbCallbackExecutor implements ListeningExecutor { + + @Override + public ListenableFuture executeAsync(Callable task) { + try { + return Futures.immediateFuture(task.call()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void execute(Runnable command) { + command.run(); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 132383c064..9173b7143c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -18,7 +18,6 @@ package org.thingsboard.rule.engine.action; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import org.apache.commons.lang3.NotImplementedException; import org.junit.Before; import org.junit.Test; @@ -29,6 +28,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; @@ -52,7 +52,6 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import javax.script.ScriptException; import java.io.IOException; -import java.util.concurrent.Callable; import java.util.function.Consumer; import static org.junit.Assert.assertEquals; @@ -105,21 +104,7 @@ public class TbAlarmNodeTest { @Before public void before() { - dbExecutor = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(Runnable command) { - command.run(); - } - }; + dbExecutor = new TestDbCallbackExecutor(); } @Test diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java index 3b8a8c9193..542ca75a51 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java @@ -26,17 +26,17 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -79,21 +79,7 @@ public class TbCreateRelationNodeTest { @Before public void before() { - dbExecutor = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(Runnable command) { - command.run(); - } - }; + dbExecutor = new TestDbCallbackExecutor(); } @Test diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java index 98c0c62231..f44c58e663 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java @@ -40,7 +40,6 @@ import org.thingsboard.server.common.data.page.PageLink; 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.session.SessionMsgType; import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeService; @@ -54,6 +53,7 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATE import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @RunWith(MockitoJUnitRunner.class) public class TbMsgPushToEdgeNodeTest { @@ -89,7 +89,7 @@ public class TbMsgPushToEdgeNodeTest { Mockito.when(ctx.getEdgeService()).thenReturn(edgeService); Mockito.when(edgeService.findRelatedEdgeIdsByEntityId(tenantId, deviceId, new PageLink(TbMsgPushToEdgeNode.DEFAULT_PAGE_SIZE))).thenReturn(new PageData<>()); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, "{}", null, null); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java index 4ddf75f29c..3e4dd21fd2 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbAssetTypeSwitchNodeTest { @@ -121,6 +122,6 @@ class TbAssetTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(), "{}", callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}", callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java new file mode 100644 index 0000000000..0677f0b0c9 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java @@ -0,0 +1,167 @@ +/** + * 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.rule.engine.filter; + +import com.google.common.util.concurrent.Futures; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; +import org.thingsboard.rule.engine.api.RuleEngineAlarmService; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbCheckAlarmStatusNodeTest { + + private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); + private static final AlarmId ALARM_ID = new AlarmId(UUID.randomUUID()); + private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); + + private static TbCheckAlarmStatusNode node; + + private static TbContext ctx; + private static RuleEngineAlarmService alarmService; + + @BeforeEach + public void setUp() throws TbNodeException { + var config = new TbCheckAlarmStatusNodeConfig().defaultConfiguration(); + + ctx = mock(TbContext.class); + alarmService = mock(RuleEngineAlarmService.class); + + when(ctx.getTenantId()).thenReturn(TENANT_ID); + when(ctx.getAlarmService()).thenReturn(alarmService); + when(ctx.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + node = new TbCheckAlarmStatusNode(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenActiveAlarm_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var alarm = new Alarm(); + alarm.setId(ALARM_ID); + alarm.setOriginator(DEVICE_ID); + alarm.setType("General Alarm"); + + String msgData = JacksonUtil.toString(alarm); + TbMsg msg = getTbMsg(DEVICE_ID, msgData); + + when(alarmService.findAlarmByIdAsync(TENANT_ID, ALARM_ID)).thenReturn(Futures.immediateFuture(alarm)); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenClearedAlarm_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var alarm = new Alarm(); + alarm.setId(ALARM_ID); + alarm.setOriginator(DEVICE_ID); + alarm.setType("General Alarm"); + alarm.setCleared(true); + + String msgData = JacksonUtil.toString(alarm); + TbMsg msg = getTbMsg(DEVICE_ID, msgData); + + when(alarmService.findAlarmByIdAsync(TENANT_ID, ALARM_ID)).thenReturn(Futures.immediateFuture(alarm)); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenDeletedAlarm_whenOnMsg_then_Failure() throws TbNodeException { + // GIVEN + var alarm = new Alarm(); + alarm.setId(ALARM_ID); + alarm.setOriginator(DEVICE_ID); + alarm.setType("General Alarm"); + alarm.setCleared(true); + + String msgData = JacksonUtil.toString(alarm); + TbMsg msg = getTbMsg(DEVICE_ID, msgData); + + when(alarmService.findAlarmByIdAsync(TENANT_ID, ALARM_ID)).thenReturn(Futures.immediateFuture(null)); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class); + verify(ctx, times(1)).tellFailure(newMsgCaptor.capture(), throwableCaptor.capture()); + verify(ctx, never()).tellSuccess(any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + Throwable value = throwableCaptor.getValue(); + assertThat(value).isInstanceOf(TbNodeException.class).hasMessage("No such alarm found."); + } + + private TbMsg getTbMsg(EntityId entityId, String msgData) { + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), msgData); + } + + +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java index e7b3ccd7f1..fb77b5dc5d 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbDeviceTypeSwitchNodeTest { @@ -121,6 +122,6 @@ class TbDeviceTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(), "{}", callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}", callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 81e4c70ce0..0b9b2ec143 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -27,7 +27,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine; 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.TbNodeConnectionType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java new file mode 100644 index 0000000000..7814c82662 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java @@ -0,0 +1,103 @@ +/** + * 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.rule.engine.filter; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbMsgTypeFilterNodeTest { + + private DeviceId deviceId; + private TbContext ctx; + private TbMsgTypeFilterNode node; + + @BeforeEach + void setUp() throws TbNodeException { + ctx = mock(TbContext.class); + var config = new TbMsgTypeFilterNodeConfiguration().defaultConfiguration(); + deviceId = new DeviceId(UUID.randomUUID()); + node = new TbMsgTypeFilterNode(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenPostAttributes_whenOnMsg_then_True() { + // GIVEN + TbMsg msg = getTbMsg(deviceId, POST_ATTRIBUTES_REQUEST); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenAttributesUpdated_whenOnMsg_then_False() { + // GIVEN + TbMsg msg = getTbMsg(deviceId, ATTRIBUTES_UPDATED); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + private TbMsg getTbMsg(EntityId entityId, TbMsgType msgType) { + return TbMsg.newMsg(msgType.name(), entityId, new TbMsgMetaData(), "{}"); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java new file mode 100644 index 0000000000..86ba9d1fd0 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java @@ -0,0 +1,102 @@ +/** + * 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.rule.engine.filter; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbOriginatorTypeFilterNodeTest { + + private TbContext ctx; + private TbOriginatorTypeFilterNode node; + + @BeforeEach + void setUp() throws TbNodeException { + ctx = mock(TbContext.class); + var config = new TbOriginatorTypeFilterNodeConfiguration().defaultConfiguration(); + node = new TbOriginatorTypeFilterNode(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenDevice_whenOnMsg_then_True() { + // GIVEN + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenAsset_whenOnMsg_then_False() { + // GIVEN + AssetId assetId = new AssetId(UUID.randomUUID()); + TbMsg msg = getTbMsg(assetId); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + private TbMsg getTbMsg(EntityId entityId) { + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}"); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java index 86765446e0..6182d8cca5 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java @@ -16,10 +16,8 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; import org.assertj.core.api.Assertions; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -29,6 +27,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -41,13 +40,13 @@ import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.timeseries.TimeseriesService; import java.util.List; import java.util.UUID; -import java.util.concurrent.Callable; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -64,27 +63,15 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class CalculateDeltaNodeTest { private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock @@ -117,13 +104,13 @@ public class CalculateDeltaNodeTest { public void givenInvalidMsgType_whenOnMsg_thenShouldTellNextOther() { // GIVEN var msgData = "{\"pulseCounter\": 42}"; - var msg = TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); // THEN - verify(ctxMock, times(1)).tellNext(eq(msg), eq("Other")); + verify(ctxMock, times(1)).tellNext(eq(msg), eq(TbNodeConnectionType.OTHER)); verify(ctxMock, never()).tellSuccess(any()); verify(ctxMock, never()).tellFailure(any(), any()); } @@ -132,13 +119,13 @@ public class CalculateDeltaNodeTest { public void givenInvalidMsgDataType_whenOnMsg_thenShouldTellNextOther() { // GIVEN var msgData = "[]"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); // THEN - verify(ctxMock, times(1)).tellNext(eq(msg), eq("Other")); + verify(ctxMock, times(1)).tellNext(eq(msg), eq(TbNodeConnectionType.OTHER)); verify(ctxMock, never()).tellSuccess(any()); verify(ctxMock, never()).tellFailure(any(), any()); } @@ -147,13 +134,13 @@ public class CalculateDeltaNodeTest { @Test public void givenInputKeyIsNotPresent_whenOnMsg_thenShouldTellNextOther() { // GIVEN - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}"); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}"); // WHEN node.onMsg(ctxMock, msg); // THEN - verify(ctxMock, times(1)).tellNext(eq(msg), eq("Other")); + verify(ctxMock, times(1)).tellNext(eq(msg), eq(TbNodeConnectionType.OTHER)); verify(ctxMock, never()).tellSuccess(any()); verify(ctxMock, never()).tellFailure(any(), any()); } @@ -171,7 +158,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", 40.5))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); @@ -201,7 +188,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 40L))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); @@ -231,7 +218,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("temperature", "40.0"))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); @@ -265,7 +252,7 @@ public class CalculateDeltaNodeTest { var msgData = "{\"temperature\": 42,\"airPressure\":123}"; var firstMsgMetaData = new TbMsgMetaData(); firstMsgMetaData.putValue("ts", String.valueOf(3L)); - var firstMsg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, firstMsgMetaData, msgData); + var firstMsg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, firstMsgMetaData, msgData); // WHEN node.onMsg(ctxMock, firstMsg); @@ -289,7 +276,7 @@ public class CalculateDeltaNodeTest { var secondMsgMetaData = new TbMsgMetaData(); secondMsgMetaData.putValue("ts", String.valueOf(6L)); - var secondMsg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, secondMsgMetaData, msgData); + var secondMsg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, secondMsgMetaData, msgData); // WHEN node.onMsg(ctxMock, secondMsg); @@ -320,7 +307,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", null))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); @@ -348,7 +335,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("pulseCounter", 200L))); var msgData = "{\"pulseCounter\":\"123\"}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN node.onMsg(ctxMock, msg); @@ -377,7 +364,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("pulseCounter", "high"))); var msgData = "{\"pulseCounter\":\"123\"}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN-THEN Assertions.assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) @@ -391,7 +378,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry("pulseCounter", false))); var msgData = "{\"pulseCounter\":true}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN-THEN Assertions.assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) @@ -405,7 +392,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new JsonDataEntry("pulseCounter", "{\"isActive\":false}"))); var msgData = "{\"pulseCounter\":{\"isActive\":true}}"; - var msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); // WHEN-THEN Assertions.assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java index 4c9d138236..dfd054edc7 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java @@ -51,6 +51,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; import static org.thingsboard.server.common.data.security.DeviceCredentialsType.ACCESS_TOKEN; @ExtendWith(MockitoExtension.class) @@ -171,7 +172,7 @@ public class TbFetchDeviceCredentialsNodeTest { final var metaData = new TbMsgMetaData(mdMap); final String data = "{\"TestAttribute_1\": \"humidity\", \"TestAttribute_2\": \"voltage\"}"; - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, metaData, data, callbackMock); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, metaData, data, callbackMock); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index 156c3f03d7..27a13893c8 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -17,9 +17,7 @@ package org.thingsboard.rule.engine.metadata; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,6 +28,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -62,7 +61,6 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.UUID; -import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -77,6 +75,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetCustomerAttributeNodeTest { @@ -84,21 +83,7 @@ public class TbGetCustomerAttributeNodeTest { private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); private static final CustomerId CUSTOMER_ID = new CustomerId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock @@ -223,7 +208,7 @@ public class TbGetCustomerAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -238,7 +223,7 @@ public class TbGetCustomerAttributeNodeTest { // GIVEN var userId = new UserId(UUID.randomUUID()); - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", userId, new TbMsgMetaData(), "{}"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), userId, new TbMsgMetaData(), "{}"); when(ctxMock.getTenantId()).thenReturn(TENANT_ID); @@ -482,7 +467,7 @@ public class TbGetCustomerAttributeNodeTest { var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index 2830dddaeb..d90d9eb767 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -17,8 +17,6 @@ package org.thingsboard.rule.engine.metadata; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -28,6 +26,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -60,7 +59,6 @@ import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.UUID; -import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -70,27 +68,14 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetCustomerDetailsNodeTest { private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock @@ -471,7 +456,7 @@ public class TbGetCustomerDetailsNodeTest { var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), originator, msgMetaData, msgData); } private void mockFindCustomer() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index 4f03fe54c0..fc61bc4849 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -16,9 +16,6 @@ package org.thingsboard.rule.engine.metadata; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -28,6 +25,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -43,7 +41,6 @@ import org.thingsboard.server.dao.device.DeviceService; import java.util.Collections; import java.util.Map; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import static org.assertj.core.api.Assertions.assertThat; @@ -54,27 +51,14 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetOriginatorFieldsNodeTest { private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId DUMMY_TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock @@ -178,7 +162,7 @@ public class TbGetOriginatorFieldsNodeTest { node.fetchTo = FetchTo.DATA; var msgMetaData = new TbMsgMetaData(); var msgData = "{\"temp\":42,\"humidity\":77}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -221,7 +205,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -269,7 +253,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -327,7 +311,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", new DashboardId(UUID.randomUUID()), msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), new DashboardId(UUID.randomUUID()), msgMetaData, msgData); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java index 23ceb082bc..d2c452afd4 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java @@ -17,9 +17,7 @@ package org.thingsboard.rule.engine.metadata; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,6 +28,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -60,7 +59,6 @@ import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.relation.RelationService; @@ -72,7 +70,6 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.UUID; -import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -87,27 +84,14 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetRelatedAttributeNodeTest { private static final EntityId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock @@ -238,7 +222,7 @@ public class TbGetRelatedAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -607,7 +591,7 @@ public class TbGetRelatedAttributeNodeTest { msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; } - msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java index d8532247ae..0ee3512288 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java @@ -17,9 +17,7 @@ package org.thingsboard.rule.engine.metadata; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,7 +27,7 @@ import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -53,7 +51,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -65,27 +62,14 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetTenantAttributeNodeTest { private static final DeviceId DUMMY_DEVICE_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock @@ -204,7 +188,7 @@ public class TbGetTenantAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -412,7 +396,7 @@ public class TbGetTenantAttributeNodeTest { var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java index 6e1e619eab..0a756575e2 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java @@ -49,6 +49,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetTenantDetailsNodeTest { @@ -286,7 +287,7 @@ public class TbGetTenantDetailsNodeTest { var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; - msg = TbMsg.newMsg("POST_TELEMETRY_REQUEST", DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); } private void mockFindTenant() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java index b480719f64..0d94d9de16 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java @@ -43,7 +43,6 @@ import org.thingsboard.server.common.data.query.EntityKeyValueType; import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.DeviceService; @@ -109,7 +108,7 @@ public class DeviceStateTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); DeviceState deviceState = createDeviceState(deviceId, alarmConfig); - TbMsg attributeUpdateMsg = TbMsg.newMsg(SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), + TbMsg attributeUpdateMsg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), deviceId, new TbMsgMetaData(), "{ \"enabled\": false }"); deviceState.process(ctx, attributeUpdateMsg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index 80ed769a71..20c529646c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -64,7 +64,6 @@ import org.thingsboard.server.common.data.query.NumericFilterPredicate; 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.session.SessionMsgType; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; @@ -86,6 +85,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @RunWith(MockitoJUnitRunner.class) public class TbDeviceProfileNodeTest { @@ -141,7 +141,7 @@ public class TbDeviceProfileNodeTest { Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 42); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); @@ -198,7 +198,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 42); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); @@ -211,7 +211,7 @@ public class TbDeviceProfileNodeTest { registerCreateAlarmMock(alarmService.updateAlarm(any()), false); - TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); verify(ctx).tellSuccess(msg2); @@ -292,7 +292,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 21); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -379,7 +379,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 21); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -448,7 +448,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -542,7 +542,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -557,7 +557,7 @@ public class TbDeviceProfileNodeTest { Thread.sleep(halfOfAlarmDelay); - TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -666,7 +666,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -681,7 +681,7 @@ public class TbDeviceProfileNodeTest { Thread.sleep(halfOfAlarmDelay); - TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -775,7 +775,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -784,7 +784,7 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); data.put("temperature", 151); - TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -891,7 +891,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -900,7 +900,7 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); data.put("temperature", 151); - TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -987,7 +987,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1002,7 +1002,7 @@ public class TbDeviceProfileNodeTest { Thread.sleep(halfOfAlarmDelay); - TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -1085,7 +1085,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1167,7 +1167,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); // Mockito.reset(ctx); @@ -1261,7 +1261,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1341,7 +1341,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 25); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1414,7 +1414,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 40); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1497,7 +1497,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150L); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1582,7 +1582,7 @@ public class TbDeviceProfileNodeTest { ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150L); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java index 27faa8313a..a72d343d10 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java @@ -31,10 +31,10 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; 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.session.SessionMsgType; import org.thingsboard.server.dao.edge.EdgeEventService; import java.util.UUID; @@ -80,7 +80,7 @@ public class TbSendRPCReplyNodeTest { Mockito.when(ctx.getRpcService()).thenReturn(rpcService); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, getDefaultMetadata(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, getDefaultMetadata(), TbMsgDataType.JSON, DUMMY_DATA, null, null); node.onMsg(ctx, msg); @@ -99,7 +99,7 @@ public class TbSendRPCReplyNodeTest { TbMsgMetaData defaultMetadata = getDefaultMetadata(); defaultMetadata.putValue(DataConstants.EDGE_ID, UUID.randomUUID().toString()); defaultMetadata.putValue(DataConstants.DEVICE_ID, UUID.randomUUID().toString()); - TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetadata, + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetadata, TbMsgDataType.JSON, DUMMY_DATA, null, null); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java index c0839f5b61..9b23a3aa31 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java @@ -53,6 +53,7 @@ import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_MET import static org.thingsboard.server.common.data.DataConstants.SCOPE; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; @Slf4j public class TbMsgDeleteAttributesNodeTest { @@ -140,7 +141,7 @@ public class TbMsgDeleteAttributesNodeTest { } final String data = "{\"TestAttribute_2\": \"humidity\", \"TestAttribute_3\": \"voltage\"}"; - TbMsg msg = TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", deviceId, metaData, data, callback); + TbMsg msg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), deviceId, metaData, data, callback); node.onMsg(ctx, msg); ArgumentCaptor successCaptor = ArgumentCaptor.forClass(Runnable.class); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java index f4cd097878..071ae9ce96 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java @@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.transform; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,6 +25,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -41,7 +41,6 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.asset.AssetService; import java.util.NoSuchElementException; -import java.util.concurrent.Callable; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -66,21 +65,7 @@ public class TbChangeOriginatorNodeTest { @Before public void before() { - dbExecutor = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(Runnable command) { - command.run(); - } - }; + dbExecutor = new TestDbCallbackExecutor(); } @Test diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java index 2666cae477..a2bfb26b4e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbCopyKeysNodeTest { DeviceId deviceId; @@ -157,7 +158,7 @@ public class TbCopyKeysNodeTest { "voltageDataValue", "220", "city", "NY" ); - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java index 7152c25acd..c73e1eec15 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbDeleteKeysNodeTest { DeviceId deviceId; @@ -140,7 +141,7 @@ public class TbDeleteKeysNodeTest { "voltageDataValue", "220", "city", "NY" ); - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java index 0ca486a3e0..91db8dfd26 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbJsonPathNodeTest { DeviceId deviceId; @@ -170,6 +171,6 @@ public class TbJsonPathNodeTest { Map mdMap = Map.of("country", "US", "city", "NY" ); - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java index f48a9f670c..ba17187297 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java @@ -30,7 +30,6 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; 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.TbNodeConnectionType; import org.thingsboard.rule.engine.deduplication.DeduplicationStrategy; import org.thingsboard.rule.engine.deduplication.TbMsgDeduplicationNode; import org.thingsboard.rule.engine.deduplication.TbMsgDeduplicationNodeConfiguration; @@ -39,9 +38,9 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.msg.session.SessionMsgType; import java.util.ArrayList; import java.util.List; @@ -66,6 +65,8 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @Slf4j public class TbMsgDeduplicationNodeTest { @@ -242,7 +243,7 @@ public class TbMsgDeduplicationNodeTest { config.setInterval(deduplicationInterval); config.setStrategy(DeduplicationStrategy.ALL); - config.setOutMsgType(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()); + config.setOutMsgType(POST_ATTRIBUTES_REQUEST.name()); config.setQueueName(DataConstants.HP_QUEUE_NAME); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -282,7 +283,7 @@ public class TbMsgDeduplicationNodeTest { config.setInterval(deduplicationInterval); config.setStrategy(DeduplicationStrategy.ALL); - config.setOutMsgType(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()); + config.setOutMsgType(POST_ATTRIBUTES_REQUEST.name()); config.setQueueName(DataConstants.HP_QUEUE_NAME); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -414,7 +415,7 @@ public class TbMsgDeduplicationNodeTest { metaData.putValue("ts", String.valueOf(ts)); return TbMsg.newMsg( DataConstants.MAIN_QUEUE_NAME, - SessionMsgType.POST_TELEMETRY_REQUEST.name(), + POST_TELEMETRY_REQUEST.name(), deviceId, metaData, JacksonUtil.toString(dataNode)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java index 9eade69e4b..0caa7f74f3 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java @@ -40,6 +40,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbRenameKeysNodeTest { DeviceId deviceId; @@ -154,6 +155,6 @@ public class TbRenameKeysNodeTest { "country", "US", "city", "NY" ); - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java index 67ebf6f6fc..cf1eee085c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java @@ -43,6 +43,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbSplitArrayMsgNodeTest { DeviceId deviceId; @@ -132,6 +133,6 @@ public class TbSplitArrayMsgNodeTest { "country", "US", "city", "NY" ); - return TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java index 957f98d721..c675b97d08 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoaderTest.java @@ -16,13 +16,12 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.Customer; @@ -41,7 +40,6 @@ import org.thingsboard.server.dao.user.UserService; import java.util.EnumSet; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -60,21 +58,7 @@ public class EntitiesCustomerIdAsyncLoaderTest { EntityType.ASSET, EntityType.DEVICE ); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java index e835fa6e78..d60f2256e1 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java @@ -16,8 +16,6 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -25,6 +23,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; @@ -61,7 +60,6 @@ import org.thingsboard.server.dao.user.UserService; import java.util.EnumSet; import java.util.NoSuchElementException; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import static org.assertj.core.api.Assertions.assertThat; @@ -75,21 +73,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class EntitiesFieldsAsyncLoaderTest { - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); private static EnumSet SUPPORTED_ENTITY_TYPES; private static UUID RANDOM_UUID; private static TenantId TENANT_ID; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java index 546a726f07..c54072fb53 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoaderTest.java @@ -16,13 +16,12 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.data.DeviceRelationsQuery; import org.thingsboard.server.common.data.Device; @@ -37,7 +36,6 @@ import org.thingsboard.server.dao.device.DeviceService; import java.util.List; import java.util.UUID; -import java.util.concurrent.Callable; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -51,21 +49,7 @@ public class EntitiesRelatedDeviceIdAsyncLoaderTest { private static final EntityId DUMMY_ORIGINATOR = new DeviceId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); @Mock private TbContext ctxMock; @Mock diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoaderTest.java index b29e451eaf..e51a3704d1 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoaderTest.java @@ -17,11 +17,11 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.data.RelationsQuery; import org.thingsboard.server.common.data.Device; @@ -41,7 +41,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; -import java.util.concurrent.Callable; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -57,21 +56,7 @@ public class EntitiesRelatedEntityIdAsyncLoaderTest { private static final EntityId ASSET_ORIGINATOR_ID = new AssetId(UUID.randomUUID()); private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); - private static final ListeningExecutor DB_EXECUTOR = new ListeningExecutor() { - @Override - public ListenableFuture executeAsync(Callable task) { - try { - return Futures.immediateFuture(task.call()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void execute(@NotNull Runnable command) { - command.run(); - } - }; + private static final ListeningExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); private TbContext ctxMock; private RelationService relationServiceMock; From 5acd5b36585f7a08f50ace494beece9979eae047 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 26 Jun 2023 11:45:20 +0300 Subject: [PATCH 011/166] refactoring --- .../server/controller/DeviceControllerTest.java | 6 +++--- .../server/dao/device/DeviceServiceImpl.java | 14 ++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 00ccdb1119..96aa5638db 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -662,11 +662,11 @@ public class DeviceControllerTest extends AbstractControllerTest { doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); assertThat(commands).hasSize(3); - assertThat(commands).containsExactly(String.format("mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -u %s -m \"{temperature:15}\"", + assertThat(commands).containsExactly(String.format("mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId()), - String.format("curl -v -X POST http://localhost:80/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:16}\"", + String.format("curl -v -X POST http://localhost:80/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", credentials.getCredentialsId()), - String.format("echo -n \"{temperature:17}\" | coap-client -m post coap://localhost:5683/api/v1/%s/telemetry -f-", + String.format("echo -n \"{temperature:25}\" | coap-client -m post coap://localhost:5683/api/v1/%s/telemetry -f-", credentials.getCredentialsId())); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 85aab629d8..82d380056b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -103,6 +103,8 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Wed, 28 Jun 2023 12:43:49 +0300 Subject: [PATCH 012/166] refactoring & added tests for alarm status filter node & entity and msg type switch nodes & check field presence --- .../actors/ruleChain/DefaultTbContext.java | 13 +- .../DefaultSystemDataLoaderService.java | 3 +- .../server/common/data/DataConstants.java | 1 + .../server/common/data/StringUtils.java | 2 +- .../AbstractGatewaySessionHandler.java | 2 +- .../external/TbAbstractExternalNode.java | 12 +- .../filter/TbAssetTypeSwitchNodeTest.java | 1 + .../filter/TbCheckAlarmStatusNodeTest.java | 3 +- .../engine/filter/TbCheckMessageNodeTest.java | 206 ++++++++++++++++++ .../filter/TbMsgTypeSwitchNodeTest.java | 97 +++++++++ .../TbOriginatorTypeSwitchNodeTest.java | 98 +++++++++ 11 files changed, 413 insertions(+), 25 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 2d087de74e..f9d1940714 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -852,17 +852,10 @@ class DefaultTbContext implements TbContext { } private static String getFailureMessage(Throwable th) { - String failureMessage; - if (th != null) { - if (!StringUtils.isEmpty(th.getMessage())) { - failureMessage = th.getMessage(); - } else { - failureMessage = th.getClass().getSimpleName(); - } - } else { - failureMessage = null; + if (th == null) { + return null; } - return failureMessage; + return StringUtils.isNotEmpty(th.getMessage()) ? th.getMessage() : th.getClass().getSimpleName(); } private class SimpleTbQueueCallback implements TbQueueCallback { diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 7965824274..1087990c4e 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -114,13 +114,14 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import static org.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE; + @Service @Profile("install") @Slf4j public class DefaultSystemDataLoaderService implements SystemDataLoaderService { public static final String CUSTOMER_CRED = "customer"; - public static final String DEFAULT_DEVICE_TYPE = "default"; public static final String ACTIVITY_STATE = "active"; @Autowired diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 6e7341fa24..ed4431f445 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -65,6 +65,7 @@ public class DataConstants { public static final String PROVISION_KEY = "provisionDeviceKey"; public static final String PROVISION_SECRET = "provisionDeviceSecret"; + public static final String DEFAULT_DEVICE_TYPE = "default"; public static final String DEVICE_NAME = "deviceName"; public static final String DEVICE_TYPE = "deviceType"; public static final String CERT_PUB_KEY = "x509CertPubKey"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java index a7671f4327..6b70dfc09c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java @@ -42,7 +42,7 @@ public class StringUtils { } public static boolean isNotEmpty(String source) { - return source != null && !source.isEmpty(); + return !isEmpty(source); } public static boolean isNotBlank(String source) { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java index 95603e751c..5cd6ba9145 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java @@ -71,6 +71,7 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static org.springframework.util.ConcurrentReferenceHashMap.ReferenceType; +import static org.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE; import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN; import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG; @@ -85,7 +86,6 @@ import static org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMess @Slf4j public abstract class AbstractGatewaySessionHandler { - protected static final String DEFAULT_DEVICE_TYPE = "default"; private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; private static final String DEVICE_PROPERTY = "device"; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java index d9d25bc4cd..8402e87076 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java @@ -38,17 +38,9 @@ public abstract class TbAbstractExternalNode implements TbNode { protected void tellFailure(TbContext ctx, TbMsg tbMsg, Throwable t) { if (forceAck) { - if (t == null) { - ctx.enqueueForTellNext(tbMsg.copyWithNewCtx(), TbNodeConnectionType.FAILURE); - } else { - ctx.enqueueForTellFailure(tbMsg.copyWithNewCtx(), t); - } + ctx.enqueueForTellFailure(tbMsg.copyWithNewCtx(), t); } else { - if (t == null) { - ctx.tellNext(tbMsg, TbNodeConnectionType.FAILURE); - } else { - ctx.tellFailure(tbMsg, t); - } + ctx.tellFailure(tbMsg, t); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java index 3e4dd21fd2..7ef2f87845 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java @@ -124,4 +124,5 @@ class TbAssetTypeSwitchNodeTest { private TbMsg getTbMsg(EntityId entityId) { return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}", callback); } + } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java index 0677f0b0c9..4212af2ade 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java @@ -163,5 +163,4 @@ class TbCheckAlarmStatusNodeTest { return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), msgData); } - -} \ No newline at end of file +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java new file mode 100644 index 0000000000..dd25363aed --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java @@ -0,0 +1,206 @@ +/** + * 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.rule.engine.filter; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE; +import static org.thingsboard.server.common.data.DataConstants.DEVICE_NAME; +import static org.thingsboard.server.common.data.DataConstants.DEVICE_TYPE; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbCheckMessageNodeTest { + + private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); + private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); + private static final String EMPTY_DATA = "{}"; + private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, EMPTY_METADATA, EMPTY_DATA); + + private static TbCheckMessageNode node; + + private static TbContext ctx; + + @BeforeEach + void setUp() { + ctx = mock(TbContext.class); + node = new TbCheckMessageNode(); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenDefaultConfig_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var configuration = new TbCheckMessageNodeConfiguration().defaultConfiguration(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(configuration))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigWithoutCheckAllKeysAndWithEmptyLists_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var configuration = new TbCheckMessageNodeConfiguration().defaultConfiguration(); + configuration.setCheckAllKeys(false); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(configuration))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigWithCheckAllKeys_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var configuration = new TbCheckMessageNodeConfiguration().defaultConfiguration(); + configuration.setMessageNames(List.of("temperature-0")); + configuration.setMetadataNames(List.of("deviceName", "deviceType", "ts")); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(configuration))); + + TbMsg tbMsg = getTbMsg(); + + // WHEN + node.onMsg(ctx, tbMsg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(tbMsg); + } + + @Test + void givenCustomConfigWithCheckAllKeys_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var configuration = new TbCheckMessageNodeConfiguration().defaultConfiguration(); + configuration.setMessageNames(List.of("temperature-0", "temperature-1")); + configuration.setMetadataNames(List.of("deviceName", "deviceType", "ts")); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(configuration))); + + TbMsg tbMsg = getTbMsg(); + + // WHEN + node.onMsg(ctx, tbMsg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(tbMsg); + } + + @Test + void givenCustomConfigWithoutCheckAllKeys_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var configuration = new TbCheckMessageNodeConfiguration().defaultConfiguration(); + configuration.setMessageNames(List.of("temperature-0", "temperature-1")); + configuration.setCheckAllKeys(false); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(configuration))); + + TbMsg tbMsg = getTbMsg(); + + // WHEN + node.onMsg(ctx, tbMsg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(tbMsg); + } + + @Test + void givenCustomConfigWithoutCheckAllKeysAndEmptyMsg_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var configuration = new TbCheckMessageNodeConfiguration().defaultConfiguration(); + configuration.setMessageNames(List.of("temperature-0", "temperature-1")); + configuration.setCheckAllKeys(false); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(configuration))); + + TbMsg tbMsg = getTbMsg(true); + + // WHEN + node.onMsg(ctx, tbMsg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(tbMsg); + } + + private TbMsg getTbMsg() { + return getTbMsg(false); + } + + private TbMsg getTbMsg(boolean emptyData) { + String data = emptyData ? EMPTY_DATA : "{\"temperature-0\": 25}"; + var metadata = new TbMsgMetaData(); + metadata.putValue(DEVICE_NAME, "Test Device"); + metadata.putValue(DEVICE_TYPE, DEFAULT_DEVICE_TYPE); + metadata.putValue("ts", String.valueOf(System.currentTimeMillis())); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, metadata, data); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java new file mode 100644 index 0000000000..51155688e6 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java @@ -0,0 +1,97 @@ +/** + * 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.rule.engine.filter; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +class TbMsgTypeSwitchNodeTest { + + private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); + private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); + private static final String EMPTY_DATA = "{}"; + + private static TbMsgTypeSwitchNode node; + + private static TbContext ctx; + + @BeforeEach + void setUp() { + ctx = mock(TbContext.class); + node = new TbMsgTypeSwitchNode(); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenAllTypes_whenOnMsg_then_allTypesSupported() throws TbNodeException { + // GIVEN + List tbMsgList = new ArrayList<>(); + var tbMsgTypes = TbMsgType.values(); + for (var msgType : tbMsgTypes) { + tbMsgList.add(getTbMsg(msgType)); + } + + // WHEN + for (TbMsg tbMsg : tbMsgList) { + node.onMsg(ctx, tbMsg); + } + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor nodeConnectionCapture = ArgumentCaptor.forClass(String.class); + verify(ctx, times(tbMsgList.size())).tellNext(newMsgCaptor.capture(), nodeConnectionCapture.capture()); + verify(ctx, never()).tellFailure(any(), any()); + var resultMsgs = newMsgCaptor.getAllValues(); + var resultNodeConnections = nodeConnectionCapture.getAllValues(); + for (int i = 0; i < resultMsgs.size(); i++) { + var msg = resultMsgs.get(i); + assertThat(msg).isNotNull(); + assertThat(msg.getType()).isNotNull(); + assertThat(msg).isSameAs(tbMsgList.get(i)); + // todo add additional validation that types like ALARM or PROVISION returns OTHER for backward-compatibility. + assertThat(resultNodeConnections.get(i)) + .isEqualTo(TbMsgType.getRuleNodeConnection(msg.getType())); + } + } + + private TbMsg getTbMsg(TbMsgType msgType) { + return TbMsg.newMsg(msgType.name(), DEVICE_ID, EMPTY_METADATA, EMPTY_DATA); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java new file mode 100644 index 0000000000..09e67e45ef --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java @@ -0,0 +1,98 @@ +/** + * 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.rule.engine.filter; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbOriginatorTypeSwitchNodeTest { + + private static final UUID RANDOM_UUID = UUID.randomUUID(); + private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); + private static final String EMPTY_DATA = "{}"; + + private static TbOriginatorTypeSwitchNode node; + + private static TbContext ctx; + + @BeforeEach + void setUp() { + ctx = mock(TbContext.class); + node = new TbOriginatorTypeSwitchNode(); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenAllTypes_whenOnMsg_then_allTypesSupported() throws TbNodeException { + // GIVEN + List tbMsgList = new ArrayList<>(); + var entityTypes = EntityType.values(); + for (var entityType : entityTypes) { + var entityId = EntityIdFactory.getByTypeAndUuid(entityType, RANDOM_UUID); + tbMsgList.add(getTbMsg(entityId)); + } + + // WHEN + for (TbMsg tbMsg : tbMsgList) { + node.onMsg(ctx, tbMsg); + } + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor nodeConnectionCapture = ArgumentCaptor.forClass(String.class); + verify(ctx, times(tbMsgList.size())).tellNext(newMsgCaptor.capture(), nodeConnectionCapture.capture()); + verify(ctx, never()).tellFailure(any(), any()); + var resultMsgs = newMsgCaptor.getAllValues(); + var resultNodeConnections = nodeConnectionCapture.getAllValues(); + for (int i = 0; i < resultMsgs.size(); i++) { + var msg = resultMsgs.get(i); + assertThat(msg).isNotNull(); + assertThat(msg).isSameAs(tbMsgList.get(i)); + assertThat(resultNodeConnections.get(i)) + .isEqualTo(msg.getOriginator().getEntityType().getNormalName()); + } + } + + private TbMsg getTbMsg(EntityId entityId) { + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, EMPTY_METADATA, EMPTY_DATA); + } + +} From 27b1d3f5d56a84ac1c90fd59004eba49c96a6459 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 28 Jun 2023 12:49:17 +0300 Subject: [PATCH 013/166] fix typo in NashornJsInvokeServiceTest --- .../server/service/script/NashornJsInvokeServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java index 8d7de23303..5cd54a24eb 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java @@ -122,7 +122,7 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest { } private String invokeScript(UUID scriptId, String msg) throws ExecutionException, InterruptedException { - return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.getRuleNodeConnection()).get().toString(); + return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.name()).get().toString(); } } From f01b2d6595dbf997da1cf4b4004560481fec19a3 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 28 Jun 2023 12:59:27 +0300 Subject: [PATCH 014/166] fix typo in TbelInvokeServiceTest --- .../server/service/script/TbelInvokeServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java index 2895c97c90..62af9cb16e 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java @@ -217,7 +217,7 @@ class TbelInvokeServiceTest extends AbstractControllerTest { private String invokeScript(UUID scriptId, String str) throws ExecutionException, InterruptedException { var msg = JacksonUtil.fromString(str, Map.class); - return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.getRuleNodeConnection()).get().toString(); + return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.name()).get().toString(); } } From 8beb81cf8d5ff1d7fe652c3bd1bcb61a6c61402c Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 30 Jun 2023 13:35:03 +0300 Subject: [PATCH 015/166] added tests for check relation presence node & test for TbMsgType and ActionType & refactoring --- .../device/DeviceProvisionServiceImpl.java | 2 +- .../processor/device/DeviceEdgeProcessor.java | 3 +- .../server/common/data/msg/TbMsgType.java | 4 +- .../common/data/audit/ActionTypeTest.java | 63 ++++ .../server/common/data/msg/TbMsgTypeTest.java | 70 +++++ .../engine/filter/TbCheckRelationNode.java | 10 +- .../engine/filter/TbMsgTypeSwitchNode.java | 2 +- .../filter/TbAssetTypeSwitchNodeTest.java | 27 +- .../filter/TbCheckAlarmStatusNodeTest.java | 20 +- .../engine/filter/TbCheckMessageNodeTest.java | 4 +- .../filter/TbCheckRelationNodeTest.java | 297 ++++++++++++++++++ .../filter/TbDeviceTypeSwitchNodeTest.java | 22 +- .../engine/filter/TbJsFilterNodeTest.java | 4 +- .../engine/filter/TbJsSwitchNodeTest.java | 4 +- .../filter/TbMsgTypeSwitchNodeTest.java | 7 +- .../TbOriginatorTypeSwitchNodeTest.java | 4 +- .../TbGetCustomerAttributeNodeTest.java | 6 +- .../TbGetCustomerDetailsNodeTest.java | 2 +- .../TbGetOriginatorFieldsNodeTest.java | 8 +- .../TbGetRelatedAttributeNodeTest.java | 2 +- .../TbGetTenantAttributeNodeTest.java | 4 +- .../metadata/TbGetTenantDetailsNodeTest.java | 2 +- 22 files changed, 498 insertions(+), 69 deletions(-) create mode 100644 common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java create mode 100644 common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 3bed8a1905..d5614c2193 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -273,7 +273,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { TbMsg msg = TbMsg.newMsg(ENTITY_CREATED.name(), device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); sendToRuleEngine(device.getTenantId(), msg, null); } catch (JsonProcessingException | IllegalArgumentException e) { - log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), ENTITY_CREATED, e); + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), ENTITY_CREATED.name(), e); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java index 3848828522..49f2e0ead2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java @@ -63,6 +63,7 @@ import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; import java.util.UUID; import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.TO_SERVER_RPC_REQUEST; @Component @Slf4j @@ -218,7 +219,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { ObjectNode data = JacksonUtil.newObjectNode(); data.put("method", deviceRpcCallMsg.getRequestMsg().getMethod()); data.put("params", deviceRpcCallMsg.getRequestMsg().getParams()); - TbMsg tbMsg = TbMsg.newMsg(TbMsgType.TO_SERVER_RPC_REQUEST.name(), deviceId, null, metaData, + TbMsg tbMsg = TbMsg.newMsg(TO_SERVER_RPC_REQUEST.name(), deviceId, null, metaData, TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(data)); tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java index a974d8adbd..1872fd676f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java @@ -42,7 +42,7 @@ public enum TbMsgType { ALARM(null), ALARM_ACK("Alarm Acknowledged"), ALARM_CLEAR("Alarm Cleared"), - ALARM_DELETE("Alarm Deleted"), + ALARM_DELETE(null), ALARM_ASSIGNED("Alarm Assigned"), ALARM_UNASSIGNED("Alarm Unassigned"), COMMENT_CREATED("Comment Created"), @@ -78,7 +78,7 @@ public enum TbMsgType { this.ruleNodeConnection = ruleNodeConnection; } - public static String getRuleNodeConnection(String msgType) { + public static String getRuleNodeConnectionOrElseOther(String msgType) { if (msgType == null) { return TbNodeConnectionType.OTHER; } else { diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java new file mode 100644 index 0000000000..b76b0fc2b7 --- /dev/null +++ b/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java @@ -0,0 +1,63 @@ +/** + * 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.audit; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.thingsboard.server.common.data.audit.ActionType.ACTIVATED; +import static org.thingsboard.server.common.data.audit.ActionType.ATTRIBUTES_READ; +import static org.thingsboard.server.common.data.audit.ActionType.CREDENTIALS_READ; +import static org.thingsboard.server.common.data.audit.ActionType.CREDENTIALS_UPDATED; +import static org.thingsboard.server.common.data.audit.ActionType.DELETED_COMMENT; +import static org.thingsboard.server.common.data.audit.ActionType.LOCKOUT; +import static org.thingsboard.server.common.data.audit.ActionType.LOGIN; +import static org.thingsboard.server.common.data.audit.ActionType.LOGOUT; +import static org.thingsboard.server.common.data.audit.ActionType.RPC_CALL; +import static org.thingsboard.server.common.data.audit.ActionType.SMS_SENT; +import static org.thingsboard.server.common.data.audit.ActionType.SUSPENDED; + +class ActionTypeTest { + + private static final List typesWithNullRuleEngineMsgType = List.of( + RPC_CALL, + CREDENTIALS_UPDATED, + ACTIVATED, + SUSPENDED, + CREDENTIALS_READ, + ATTRIBUTES_READ, + LOGIN, + LOGOUT, + LOCKOUT, + DELETED_COMMENT, + SMS_SENT + ); + + // backward-compatibility tests + + @Test + void getRuleEngineMsgTypeTest() { + var types = ActionType.values(); + for (var type : types) { + if (typesWithNullRuleEngineMsgType.contains(type)) { + assertThat(type.getRuleEngineMsgType()).isEmpty(); + } + } + } + +} diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java new file mode 100644 index 0000000000..c1f9dffd17 --- /dev/null +++ b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java @@ -0,0 +1,70 @@ +/** + * 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.msg; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED_TO_EDGE; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UNASSIGNED_FROM_EDGE; +import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_FAILURE; +import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_SUCCESS; + +class TbMsgTypeTest { + + private static final List typesWithNullRuleNodeConnection = List.of( + ALARM, + ALARM_DELETE, + ENTITY_ASSIGNED_TO_EDGE, + ENTITY_UNASSIGNED_FROM_EDGE, + PROVISION_FAILURE, + PROVISION_SUCCESS + ); + + + // backward-compatibility tests + + @Test + void getRuleNodeConnectionsTest() { + var tbMsgTypes = TbMsgType.values(); + for (var type : tbMsgTypes) { + if (typesWithNullRuleNodeConnection.contains(type)) { + assertThat(type.getRuleNodeConnection()).isNull(); + } + } + } + + @Test + void getRuleNodeConnectionOrElseOtherTest() { + assertThat(TbMsgType.getRuleNodeConnectionOrElseOther(null)) + .isEqualTo(TbNodeConnectionType.OTHER); + var tbMsgTypes = TbMsgType.values(); + for (var type : tbMsgTypes) { + if (typesWithNullRuleNodeConnection.contains(type)) { + assertThat(TbMsgType.getRuleNodeConnectionOrElseOther(type.name())) + .isEqualTo(TbNodeConnectionType.OTHER); + } else { + assertThat(TbMsgType.getRuleNodeConnectionOrElseOther(type.name())).isNotNull() + .isNotEqualTo(TbNodeConnectionType.OTHER); + } + } + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index 3dfa6cb233..2186378527 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -22,6 +22,7 @@ import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; @@ -62,6 +63,9 @@ public class TbCheckRelationNode implements TbNode { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbCheckRelationNodeConfiguration.class); if (config.isCheckForSingleEntity()) { + if (StringUtils.isEmpty(config.getEntityType()) || StringUtils.isEmpty(config.getEntityId())) { + throw new TbNodeException("Entity should be specified!"); + } this.singleEntityId = EntityIdFactory.getByTypeAndId(config.getEntityType(), config.getEntityId()); ctx.checkTenantEntity(singleEntityId); } @@ -90,9 +94,9 @@ public class TbCheckRelationNode implements TbNode { } private ListenableFuture processList(TbContext ctx, TbMsg msg) { - ListenableFuture> relationListFuture = EntitySearchDirection.FROM.name().equals(config.getDirection()) ? ctx.getRelationService() - .findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON) : ctx.getRelationService() - .findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON); + ListenableFuture> relationListFuture = EntitySearchDirection.FROM.name().equals(config.getDirection()) ? + ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON) : + ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON); return Futures.transformAsync(relationListFuture, this::isEmptyList, ctx.getDbCallbackExecutor()); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index bd0d5d5160..2121e0c5fa 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -50,7 +50,7 @@ public class TbMsgTypeSwitchNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - ctx.tellNext(msg, TbMsgType.getRuleNodeConnection(msg.getType())); + ctx.tellNext(msg, TbMsgType.getRuleNodeConnectionOrElseOther(msg.getType())); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java index 7ef2f87845..a6b2433df4 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java @@ -50,34 +50,33 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R class TbAssetTypeSwitchNodeTest { - TenantId tenantId; - AssetId assetId; - AssetId assetIdDeleted; - AssetProfile assetProfile; - TbContext ctx; - TbAssetTypeSwitchNode node; - EmptyNodeConfiguration config; - TbMsgCallback callback; - RuleEngineAssetProfileCache assetProfileCache; + private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); + private static final String EMPTY_DATA = "{}"; + + private AssetId assetId; + private AssetId assetIdDeleted; + private TbContext ctx; + private TbAssetTypeSwitchNode node; + private TbMsgCallback callback; @BeforeEach void setUp() throws TbNodeException { - tenantId = new TenantId(UUID.randomUUID()); + TenantId tenantId = new TenantId(UUID.randomUUID()); assetId = new AssetId(UUID.randomUUID()); assetIdDeleted = new AssetId(UUID.randomUUID()); - assetProfile = new AssetProfile(); + AssetProfile assetProfile = new AssetProfile(); assetProfile.setTenantId(tenantId); assetProfile.setName("TestAssetProfile"); //node - config = new EmptyNodeConfiguration(); + EmptyNodeConfiguration config = new EmptyNodeConfiguration(); node = new TbAssetTypeSwitchNode(); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); //init mock ctx = mock(TbContext.class); - assetProfileCache = mock(RuleEngineAssetProfileCache.class); + RuleEngineAssetProfileCache assetProfileCache = mock(RuleEngineAssetProfileCache.class); callback = mock(TbMsgCallback.class); when(ctx.getTenantId()).thenReturn(tenantId); @@ -122,7 +121,7 @@ class TbAssetTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}", callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, EMPTY_METADATA, EMPTY_DATA, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java index 4212af2ade..a794b96620 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java @@ -29,7 +29,6 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; @@ -53,14 +52,15 @@ class TbCheckAlarmStatusNodeTest { private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); private static final AlarmId ALARM_ID = new AlarmId(UUID.randomUUID()); private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); + private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); - private static TbCheckAlarmStatusNode node; + private TbCheckAlarmStatusNode node; - private static TbContext ctx; - private static RuleEngineAlarmService alarmService; + private TbContext ctx; + private RuleEngineAlarmService alarmService; @BeforeEach - public void setUp() throws TbNodeException { + void setUp() throws TbNodeException { var config = new TbCheckAlarmStatusNodeConfig().defaultConfiguration(); ctx = mock(TbContext.class); @@ -88,7 +88,7 @@ class TbCheckAlarmStatusNodeTest { alarm.setType("General Alarm"); String msgData = JacksonUtil.toString(alarm); - TbMsg msg = getTbMsg(DEVICE_ID, msgData); + TbMsg msg = getTbMsg(msgData); when(alarmService.findAlarmByIdAsync(TENANT_ID, ALARM_ID)).thenReturn(Futures.immediateFuture(alarm)); @@ -114,7 +114,7 @@ class TbCheckAlarmStatusNodeTest { alarm.setCleared(true); String msgData = JacksonUtil.toString(alarm); - TbMsg msg = getTbMsg(DEVICE_ID, msgData); + TbMsg msg = getTbMsg(msgData); when(alarmService.findAlarmByIdAsync(TENANT_ID, ALARM_ID)).thenReturn(Futures.immediateFuture(alarm)); @@ -140,7 +140,7 @@ class TbCheckAlarmStatusNodeTest { alarm.setCleared(true); String msgData = JacksonUtil.toString(alarm); - TbMsg msg = getTbMsg(DEVICE_ID, msgData); + TbMsg msg = getTbMsg(msgData); when(alarmService.findAlarmByIdAsync(TENANT_ID, ALARM_ID)).thenReturn(Futures.immediateFuture(null)); @@ -159,8 +159,8 @@ class TbCheckAlarmStatusNodeTest { assertThat(value).isInstanceOf(TbNodeException.class).hasMessage("No such alarm found."); } - private TbMsg getTbMsg(EntityId entityId, String msgData) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), msgData); + private TbMsg getTbMsg(String msgData) { + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, EMPTY_METADATA, msgData); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java index dd25363aed..23d6711088 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java @@ -50,9 +50,9 @@ class TbCheckMessageNodeTest { private static final String EMPTY_DATA = "{}"; private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, EMPTY_METADATA, EMPTY_DATA); - private static TbCheckMessageNode node; + private TbCheckMessageNode node; - private static TbContext ctx; + private TbContext ctx; @BeforeEach void setUp() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java new file mode 100644 index 0000000000..b5cdb698a3 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java @@ -0,0 +1,297 @@ +/** + * 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.rule.engine.filter; + +import com.google.common.util.concurrent.Futures; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.relation.RelationService; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbCheckRelationNodeTest { + + private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); + private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); + private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); + private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); + private static final String EMPTY_DATA = "{}"; + private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, EMPTY_METADATA, EMPTY_DATA); + + private TbCheckRelationNode node; + + private TbContext ctx; + private RelationService relationService; + + @BeforeEach + void setUp() { + ctx = mock(TbContext.class); + relationService = mock(RelationService.class); + + when(ctx.getTenantId()).thenReturn(TENANT_ID); + when(ctx.getRelationService()).thenReturn(relationService); + when(ctx.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + node = new TbCheckRelationNode(); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + @Test + void givenDefaultConfig_whenInit_then_throwException() { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))); + + // THEN + assertThat(exception.getMessage()).isEqualTo("Entity should be specified!"); + } + + @Test + void givenCustomConfigWithCheckRelationToSpecificEntity_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + + AssetId assetId = new AssetId(UUID.randomUUID()); + config.setEntityType(assetId.getEntityType().name()); + config.setEntityId(assetId.getId().toString()); + + when(relationService.checkRelationAsync(TENANT_ID, assetId, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(true)); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigWithCheckRelationToSpecificEntity_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + + AssetId assetId = new AssetId(UUID.randomUUID()); + config.setEntityType(assetId.getEntityType().name()); + config.setEntityId(assetId.getId().toString()); + + when(relationService.checkRelationAsync(TENANT_ID, assetId, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(false)); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigWithCheckRelationToSpecificEntityAndDirectionTo_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + + AssetId assetId = new AssetId(UUID.randomUUID()); + config.setEntityType(assetId.getEntityType().name()); + config.setEntityId(assetId.getId().toString()); + config.setDirection(EntitySearchDirection.TO.name()); + + when(relationService.checkRelationAsync(TENANT_ID, DEVICE_ID, assetId, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(true)); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigWithCheckRelationToSpecificEntityAndDirectionTo_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + + AssetId assetId = new AssetId(UUID.randomUUID()); + config.setEntityType(assetId.getEntityType().name()); + config.setEntityId(assetId.getId().toString()); + config.setDirection(EntitySearchDirection.TO.name()); + + when(relationService.checkRelationAsync(TENANT_ID, DEVICE_ID, assetId, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(false)); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfig_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + config.setCheckForSingleEntity(false); + var entityRelation = new EntityRelation(); + entityRelation.setTo(DEVICE_ID); + entityRelation.setFrom(new AssetId(UUID.randomUUID())); + entityRelation.setType(EntityRelation.CONTAINS_TYPE); + entityRelation.setTypeGroup(RelationTypeGroup.COMMON); + + when(relationService.findByToAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfig_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + config.setCheckForSingleEntity(false); + + when(relationService.findByToAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList())); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigDirectionTo_whenOnMsg_then_True() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + config.setCheckForSingleEntity(false); + config.setDirection(EntitySearchDirection.TO.name()); + var entityRelation = new EntityRelation(); + entityRelation.setFrom(new AssetId(UUID.randomUUID())); + entityRelation.setTo(DEVICE_ID); + entityRelation.setType(EntityRelation.CONTAINS_TYPE); + entityRelation.setTypeGroup(RelationTypeGroup.COMMON); + + when(relationService.findByFromAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + + @Test + void givenCustomConfigDirectionTo_whenOnMsg_then_False() throws TbNodeException { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + config.setCheckForSingleEntity(false); + config.setDirection(EntitySearchDirection.TO.name()); + + when(relationService.findByFromAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList())); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + // WHEN + node.onMsg(ctx, EMPTY_POST_ATTRIBUTES_MSG); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java index fb77b5dc5d..ef76787f94 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java @@ -50,34 +50,30 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R class TbDeviceTypeSwitchNodeTest { - TenantId tenantId; - DeviceId deviceId; - DeviceId deviceIdDeleted; - DeviceProfile deviceProfile; - TbContext ctx; - TbDeviceTypeSwitchNode node; - EmptyNodeConfiguration config; - TbMsgCallback callback; - RuleEngineDeviceProfileCache deviceProfileCache; + private DeviceId deviceId; + private DeviceId deviceIdDeleted; + private TbContext ctx; + private TbDeviceTypeSwitchNode node; + private TbMsgCallback callback; @BeforeEach void setUp() throws TbNodeException { - tenantId = new TenantId(UUID.randomUUID()); + TenantId tenantId = new TenantId(UUID.randomUUID()); deviceId = new DeviceId(UUID.randomUUID()); deviceIdDeleted = new DeviceId(UUID.randomUUID()); - deviceProfile = new DeviceProfile(); + DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setTenantId(tenantId); deviceProfile.setName("TestDeviceProfile"); //node - config = new EmptyNodeConfiguration(); + EmptyNodeConfiguration config = new EmptyNodeConfiguration(); node = new TbDeviceTypeSwitchNode(); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); //init mock ctx = mock(TbContext.class); - deviceProfileCache = mock(RuleEngineDeviceProfileCache.class); + RuleEngineDeviceProfileCache deviceProfileCache = mock(RuleEngineDeviceProfileCache.class); callback = mock(TbMsgCallback.class); when(ctx.getTenantId()).thenReturn(tenantId); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 0b9b2ec143..b49dd4aac8 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -53,8 +53,8 @@ public class TbJsFilterNodeTest { @Mock private ScriptEngine scriptEngine; - private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); - private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); + private final RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); + private final RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); @Test public void falseEvaluationDoNotSendMsg() throws TbNodeException { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java index f4f852c6bd..763af2b1ee 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java @@ -47,8 +47,8 @@ public class TbJsSwitchNodeTest { @Mock private ScriptEngine scriptEngine; - private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); - private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); + private final RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); + private final RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); @Test public void multipleRoutesAreAllowed() throws TbNodeException { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java index 51155688e6..d43d309cda 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java @@ -43,9 +43,9 @@ class TbMsgTypeSwitchNodeTest { private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); private static final String EMPTY_DATA = "{}"; - private static TbMsgTypeSwitchNode node; + private TbMsgTypeSwitchNode node; - private static TbContext ctx; + private TbContext ctx; @BeforeEach void setUp() { @@ -84,9 +84,8 @@ class TbMsgTypeSwitchNodeTest { assertThat(msg).isNotNull(); assertThat(msg.getType()).isNotNull(); assertThat(msg).isSameAs(tbMsgList.get(i)); - // todo add additional validation that types like ALARM or PROVISION returns OTHER for backward-compatibility. assertThat(resultNodeConnections.get(i)) - .isEqualTo(TbMsgType.getRuleNodeConnection(msg.getType())); + .isEqualTo(TbMsgType.getRuleNodeConnectionOrElseOther(msg.getType())); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java index 09e67e45ef..28d8b55264 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java @@ -45,9 +45,9 @@ class TbOriginatorTypeSwitchNodeTest { private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); private static final String EMPTY_DATA = "{}"; - private static TbOriginatorTypeSwitchNode node; + private TbOriginatorTypeSwitchNode node; - private static TbContext ctx; + private TbContext ctx; @BeforeEach void setUp() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index 27a13893c8..12385f6e28 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -208,7 +208,7 @@ public class TbGetCustomerAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -223,7 +223,7 @@ public class TbGetCustomerAttributeNodeTest { // GIVEN var userId = new UserId(UUID.randomUUID()); - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), userId, new TbMsgMetaData(), "{}"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), userId, new TbMsgMetaData(), "{}"); when(ctxMock.getTenantId()).thenReturn(TENANT_ID); @@ -467,7 +467,7 @@ public class TbGetCustomerAttributeNodeTest { var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index d90d9eb767..540a260338 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -456,7 +456,7 @@ public class TbGetCustomerDetailsNodeTest { var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); } private void mockFindCustomer() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index fc61bc4849..9c50e35f79 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -162,7 +162,7 @@ public class TbGetOriginatorFieldsNodeTest { node.fetchTo = FetchTo.DATA; var msgMetaData = new TbMsgMetaData(); var msgData = "{\"temp\":42,\"humidity\":77}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -205,7 +205,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -253,7 +253,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -311,7 +311,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), new DashboardId(UUID.randomUUID()), msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), new DashboardId(UUID.randomUUID()), msgMetaData, msgData); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java index d2c452afd4..1a638eca39 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java @@ -222,7 +222,7 @@ public class TbGetRelatedAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java index 0ee3512288..c163d68733 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java @@ -188,7 +188,7 @@ public class TbGetTenantAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -396,7 +396,7 @@ public class TbGetTenantAttributeNodeTest { var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java index 0a756575e2..430a772269 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java @@ -287,7 +287,7 @@ public class TbGetTenantDetailsNodeTest { var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.getRuleNodeConnection(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); } private void mockFindTenant() { From e32dc47ea58b3f5e4ec92e3ec7e8b7294cc041c4 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 30 Jun 2023 17:42:53 +0300 Subject: [PATCH 016/166] added upgrade script for check field presence node && PROD-2217 --- .../engine/filter/TbCheckRelationNode.java | 44 ++++++++++++--- .../TbCheckRelationNodeConfiguration.java | 2 +- .../filter/TbCheckRelationNodeTest.java | 55 +++++++++++++------ ...nator_fields_node_fields_templatization.md | 2 +- 4 files changed, 74 insertions(+), 29 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index 2186378527..ecc06f6303 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -15,23 +15,26 @@ */ package org.thingsboard.rule.engine.filter; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbVersionedNode; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import java.util.List; @@ -54,7 +57,9 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; "Output connections: True, False, Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbFilterNodeCheckRelationConfig") -public class TbCheckRelationNode implements TbNode { +public class TbCheckRelationNode implements TbVersionedNode { + + private static final String DIRECTION_PROPERTY_NAME = "direction"; private TbCheckRelationNodeConfiguration config; private EntityId singleEntityId; @@ -84,19 +89,19 @@ public class TbCheckRelationNode implements TbNode { EntityId from; EntityId to; if (EntitySearchDirection.FROM.name().equals(config.getDirection())) { - from = singleEntityId; - to = msg.getOriginator(); - } else { to = singleEntityId; from = msg.getOriginator(); + } else { + from = singleEntityId; + to = msg.getOriginator(); } return ctx.getRelationService().checkRelationAsync(ctx.getTenantId(), from, to, config.getRelationType(), RelationTypeGroup.COMMON); } private ListenableFuture processList(TbContext ctx, TbMsg msg) { ListenableFuture> relationListFuture = EntitySearchDirection.FROM.name().equals(config.getDirection()) ? - ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON) : - ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON); + ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON) : + ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON); return Futures.transformAsync(relationListFuture, this::isEmptyList, ctx.getDbCallbackExecutor()); } @@ -104,4 +109,25 @@ public class TbCheckRelationNode implements TbNode { return entityRelations.isEmpty() ? Futures.immediateFuture(false) : Futures.immediateFuture(true); } + @Override + public TbPair upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException { + if (fromVersion == 0) { + var newConfigObjectNode = (ObjectNode) oldConfiguration; + if (!newConfigObjectNode.has(DIRECTION_PROPERTY_NAME)) { + throw new TbNodeException("property to update: '" + DIRECTION_PROPERTY_NAME + "' doesn't exists in configuration!"); + } + String direction = newConfigObjectNode.get(DIRECTION_PROPERTY_NAME).asText(); + if ("TO".equals(direction)) { + newConfigObjectNode.put(DIRECTION_PROPERTY_NAME, EntitySearchDirection.FROM.name()); + return new TbPair<>(true, newConfigObjectNode); + } + if ("FROM".equals(direction)) { + newConfigObjectNode.put(DIRECTION_PROPERTY_NAME, EntitySearchDirection.TO.name()); + return new TbPair<>(true, newConfigObjectNode); + } + throw new TbNodeException("property to update: '" + DIRECTION_PROPERTY_NAME + "' has invalid value!"); + } + return new TbPair<>(false, oldConfiguration); + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java index ad5574ec8c..1a8ab7068d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java @@ -34,7 +34,7 @@ public class TbCheckRelationNodeConfiguration implements NodeConfiguration newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); verify(ctx, never()).tellFailure(any(), any()); - verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); TbMsg newMsg = newMsgCaptor.getValue(); assertThat(newMsg).isNotNull(); assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); @@ -179,7 +183,7 @@ class TbCheckRelationNodeTest { config.setEntityId(assetId.getId().toString()); config.setDirection(EntitySearchDirection.TO.name()); - when(relationService.checkRelationAsync(TENANT_ID, DEVICE_ID, assetId, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(false)); + when(relationService.checkRelationAsync(TENANT_ID, assetId, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(false)); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); // WHEN @@ -200,12 +204,12 @@ class TbCheckRelationNodeTest { var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); config.setCheckForSingleEntity(false); var entityRelation = new EntityRelation(); - entityRelation.setTo(DEVICE_ID); - entityRelation.setFrom(new AssetId(UUID.randomUUID())); + entityRelation.setFrom(ORIGINATOR_ID); + entityRelation.setTo(new AssetId(UUID.randomUUID())); entityRelation.setType(EntityRelation.CONTAINS_TYPE); entityRelation.setTypeGroup(RelationTypeGroup.COMMON); - when(relationService.findByToAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + when(relationService.findByFromAndTypeAsync(TENANT_ID, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation))); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); // WHEN @@ -215,7 +219,7 @@ class TbCheckRelationNodeTest { ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); verify(ctx, never()).tellFailure(any(), any()); - verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); + verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any()); TbMsg newMsg = newMsgCaptor.getValue(); assertThat(newMsg).isNotNull(); assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); @@ -227,7 +231,7 @@ class TbCheckRelationNodeTest { var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); config.setCheckForSingleEntity(false); - when(relationService.findByToAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList())); + when(relationService.findByFromAndTypeAsync(TENANT_ID, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList())); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); // WHEN @@ -237,7 +241,7 @@ class TbCheckRelationNodeTest { ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); verify(ctx, never()).tellFailure(any(), any()); - verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); + verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any()); TbMsg newMsg = newMsgCaptor.getValue(); assertThat(newMsg).isNotNull(); assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); @@ -251,11 +255,11 @@ class TbCheckRelationNodeTest { config.setDirection(EntitySearchDirection.TO.name()); var entityRelation = new EntityRelation(); entityRelation.setFrom(new AssetId(UUID.randomUUID())); - entityRelation.setTo(DEVICE_ID); + entityRelation.setTo(ORIGINATOR_ID); entityRelation.setType(EntityRelation.CONTAINS_TYPE); entityRelation.setTypeGroup(RelationTypeGroup.COMMON); - when(relationService.findByFromAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation))); + when(relationService.findByToAndTypeAsync(TENANT_ID, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation))); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); // WHEN @@ -265,7 +269,7 @@ class TbCheckRelationNodeTest { ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); verify(ctx, never()).tellFailure(any(), any()); - verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any()); + verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); TbMsg newMsg = newMsgCaptor.getValue(); assertThat(newMsg).isNotNull(); assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); @@ -278,7 +282,7 @@ class TbCheckRelationNodeTest { config.setCheckForSingleEntity(false); config.setDirection(EntitySearchDirection.TO.name()); - when(relationService.findByFromAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList())); + when(relationService.findByToAndTypeAsync(TENANT_ID, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList())); node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); // WHEN @@ -288,10 +292,25 @@ class TbCheckRelationNodeTest { ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); verify(ctx, never()).tellFailure(any(), any()); - verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any()); + verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any()); TbMsg newMsg = newMsgCaptor.getValue(); assertThat(newMsg).isNotNull(); assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG); } + @Test + void givenOldConfig_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception { + // GIVEN + var config = new TbCheckRelationNodeConfiguration().defaultConfiguration(); + config.setEntityType(ORIGINATOR_ID.getEntityType().name()); + config.setEntityId(ORIGINATOR_ID.getId().toString()); + String oldConfig = "{\"checkForSingleEntity\":true,\"direction\":\"TO\",\"entityType\":\"" + config.getEntityType() + "\",\"entityId\":\"" + config.getEntityId() + "\",\"relationType\":\"Contains\"}"; + JsonNode configJson = JacksonUtil.toJsonNode(oldConfig); + // WHEN + TbPair upgrade = node.upgrade(0, configJson); + // THEN + assertTrue(upgrade.getFirst()); + assertEquals(config, JacksonUtil.treeToValue(upgrade.getSecond(), config.getClass())); + } + } diff --git a/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md b/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md index b21f450ae9..42f4ea6138 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md @@ -12,7 +12,7 @@ Let's assume that we have two device types in our use case: - `smart_door_lock` - `motion_detector` -Let's assume that device of type `dock_lock_sensor` and name `SDL-001` publish next type of messages to the system: +Let's assume that device of type `smart_door_lock` and name `SDL-001` publish next type of messages to the system: ```json { From 564b892786753aef145aa666f208b1aed435c015 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 3 Jul 2023 12:15:42 +0300 Subject: [PATCH 017/166] PROD-2240 & added missing version to check relation presence node --- .../engine/filter/TbCheckRelationNode.java | 1 + .../metadata/TbAbstractGetAttributesNode.java | 15 ++++++++++ .../metadata/TbAbstractNodeWithFetchTo.java | 28 ++++++++++++------- .../metadata/TbGetCustomerAttributeNode.java | 4 +-- .../metadata/TbGetAttributesNodeTest.java | 16 +++++++++++ .../metadata/TbGetDeviceAttrNodeTest.java | 18 ++++++++++++ 6 files changed, 69 insertions(+), 13 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index ecc06f6303..632c1af04f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -49,6 +49,7 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; type = ComponentType.FILTER, name = "check relation presence", configClazz = TbCheckRelationNodeConfiguration.class, + version = 1, relationTypes = {TbNodeConnectionType.TRUE, TbNodeConnectionType.FALSE}, nodeDescription = "Checks the presence of the relation between the originator of the message and other entities.", nodeDetails = "If 'check relation to specific entity' is selected, you should specify a related entity. " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java index ffa735fd4f..6b148f2dd4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java @@ -15,6 +15,7 @@ */ package org.thingsboard.rule.engine.metadata; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -75,6 +76,20 @@ public abstract class TbAbstractGetAttributesNode findEntityIdAsync(TbContext ctx, TbMsg msg); + protected TbPair upgradeRuleNodesWithOldPropertyToUseFetchTo( + JsonNode oldConfiguration, + String oldProperty, + String ifTrue, + String ifFalse + ) throws TbNodeException { + var newConfigObjectNode = (ObjectNode) oldConfiguration; + if (!newConfigObjectNode.has(oldProperty)) { + newConfigObjectNode.put(FETCH_TO_PROPERTY_NAME, FetchTo.METADATA.name()); + return new TbPair<>(true, newConfigObjectNode); + } + return upgradeConfigurationToUseFetchTo(oldProperty, ifTrue, ifFalse, newConfigObjectNode); + } + private void safePutAttributes(TbContext ctx, TbMsg msg, ObjectNode msgDataNode, T entityId) { Set>> failuresPairSet = ConcurrentHashMap.newKeySet(); var getKvEntryPairFutures = Futures.allAsList( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java index 16b51d02c0..2370bfde29 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java @@ -96,21 +96,29 @@ public abstract class TbAbstractNodeWithFetchTo upgradeConfigurationToUseFetchTo( + String oldProperty, String ifTrue, + String ifFalse, ObjectNode newConfig + ) throws TbNodeException { + var value = newConfig.get(oldProperty).asText(); if ("true".equals(value)) { - newConfigObjectNode.remove(oldProperty); - newConfigObjectNode.put(FETCH_TO_PROPERTY_NAME, ifTrue); - return new TbPair<>(true, newConfigObjectNode); + newConfig.remove(oldProperty); + newConfig.put(FETCH_TO_PROPERTY_NAME, ifTrue); + return new TbPair<>(true, newConfig); } else if ("false".equals(value)) { - newConfigObjectNode.remove(oldProperty); - newConfigObjectNode.put(FETCH_TO_PROPERTY_NAME, ifFalse); - return new TbPair<>(true, newConfigObjectNode); + newConfig.remove(oldProperty); + newConfig.put(FETCH_TO_PROPERTY_NAME, ifFalse); + return new TbPair<>(true, newConfig); } else { - throw new TbNodeException("property to update: '" + oldProperty + "' has unexpected value: " + value + ". Allowed values: true or false!"); + throw new TbNodeException("property to update: '" + oldProperty + "' has unexpected value: " + + value + ". Allowed values: true or false!"); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index 3cf69b5e26..f5b50f259c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -64,9 +64,7 @@ public class TbGetCustomerAttributeNode extends TbAbstractGetEntityDataNode upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException { - return fromVersion == 0 ? - upgradeToUseFetchToAndDataToFetch(oldConfiguration) : - new TbPair<>(false, oldConfiguration); + return fromVersion == 0 ? upgradeToUseFetchToAndDataToFetch(oldConfiguration) : new TbPair<>(false, oldConfiguration); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java index 5146f2f1e9..cb36982b20 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java @@ -267,6 +267,22 @@ public class TbGetAttributesNodeTest { Assertions.assertEquals(defaultConfig, JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass())); } + @Test + public void givenOldConfigWithNoFetchToDataProperty_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception { + var defaultConfig = new TbGetAttributesNodeConfiguration().defaultConfiguration(); + var node = new TbGetAttributesNode(); + String oldConfig = "{\"clientAttributeNames\":[]," + + "\"sharedAttributeNames\":[]," + + "\"serverAttributeNames\":[]," + + "\"latestTsKeyNames\":[]," + + "\"tellFailureIfAbsent\":true," + + "\"getLatestValueWithTs\":false}"; + JsonNode configJson = JacksonUtil.toJsonNode(oldConfig); + TbPair upgrade = node.upgrade(0, configJson); + Assertions.assertTrue(upgrade.getFirst()); + Assertions.assertEquals(defaultConfig, JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass())); + } + private TbMsg checkMsg(boolean checkSuccess) { var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); if (checkSuccess) { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeTest.java index 82e24226f5..bdb431227b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeTest.java @@ -42,4 +42,22 @@ public class TbGetDeviceAttrNodeTest { Assertions.assertEquals(defaultConfig, JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass())); } + @Test + public void givenOldConfigWithNoFetchToDataProperty_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception { + var defaultConfig = new TbGetDeviceAttrNodeConfiguration().defaultConfiguration(); + var node = new TbGetDeviceAttrNode(); + String oldConfig = "{\"clientAttributeNames\":[]," + + "\"sharedAttributeNames\":[]," + + "\"serverAttributeNames\":[]," + + "\"latestTsKeyNames\":[]," + + "\"tellFailureIfAbsent\":true," + + "\"getLatestValueWithTs\":false," + + "\"deviceRelationsQuery\":{\"direction\":\"FROM\",\"maxLevel\":1,\"relationType\":\"Contains\",\"deviceTypes\":[\"default\"]," + + "\"fetchLastLevelOnly\":false}}"; + JsonNode configJson = JacksonUtil.toJsonNode(oldConfig); + TbPair upgrade = node.upgrade(0, configJson); + Assertions.assertTrue(upgrade.getFirst()); + Assertions.assertEquals(defaultConfig, JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass())); + } + } \ No newline at end of file From 6c74aa3dff0552dc2dca97337743975fb6798a40 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 3 Jul 2023 13:26:20 +0300 Subject: [PATCH 018/166] updated EntityFieldsData getFieldValue method to filter out empty strings if ignoreNullStrings is set to true --- .../server/common/data/EntityFieldsData.java | 26 ++++++------ .../server/common/data/StringUtils.java | 4 +- .../TbGetOriginatorFieldsNodeTest.java | 42 +++++++++++++++++++ 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityFieldsData.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityFieldsData.java index c88a6b970f..db069d8d61 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityFieldsData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityFieldsData.java @@ -68,20 +68,22 @@ public class EntityFieldsData { break; } } - if (current != null) { - if(current.isNull() && ignoreNullStrings){ + if (current == null) { + return null; + } + if (current.isNull() && ignoreNullStrings) { + return null; + } + if (current.isValueNode()) { + String textValue = current.asText(); + if (StringUtils.isEmpty(textValue) && ignoreNullStrings) { return null; } - if (current.isValueNode()) { - return current.asText(); - } else { - try { - return mapper.writeValueAsString(current); - } catch (JsonProcessingException e) { - return null; - } - } - } else { + return textValue; + } + try { + return mapper.writeValueAsString(current); + } catch (JsonProcessingException e) { return null; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java index 6b70dfc09c..1e818ac8ef 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java @@ -38,7 +38,7 @@ public class StringUtils { } public static boolean isBlank(String source) { - return source == null || source.isEmpty() || source.trim().isEmpty(); + return isEmpty(source) || source.trim().isEmpty(); } public static boolean isNotEmpty(String source) { @@ -46,7 +46,7 @@ public class StringUtils { } public static boolean isNotBlank(String source) { - return source != null && !source.isEmpty() && !source.trim().isEmpty(); + return !isBlank(source); } public static String notBlankOrDefault(String src, String def) { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index 9c50e35f79..addd05bd0e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -184,6 +184,48 @@ public class TbGetOriginatorFieldsNodeTest { assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msgMetaData); } + @Test + public void givenDeviceWithEmptyLabel_whenOnMsg_thenShouldTellSuccessAndFetchToData() throws TbNodeException, ExecutionException, InterruptedException { + // GIVEN + var device = new Device(); + device.setId(DUMMY_DEVICE_ORIGINATOR); + device.setName("Test device"); + device.setType("Test device type"); + device.setLabel(""); + + config.setDataMapping(Map.of( + "name", "originatorName", + "type", "originatorType", + "label", "originatorLabel")); + config.setIgnoreNullStrings(true); + config.setFetchTo(FetchTo.DATA); + + node.config = config; + node.fetchTo = FetchTo.DATA; + var msgMetaData = new TbMsgMetaData(); + var msgData = "{\"temp\":42,\"humidity\":77}"; + msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + + when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); + when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); + when(deviceServiceMock.findDeviceById(eq(DUMMY_TENANT_ID), eq(device.getId()))).thenReturn(device); + + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMessageCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctxMock, times(1)).tellSuccess(actualMessageCaptor.capture()); + verify(ctxMock, never()).tellFailure(any(), any()); + + var expectedMsgData = "{\"temp\":42,\"humidity\":77,\"originatorName\":\"Test device\",\"originatorType\":\"Test device type\"}"; + + assertThat(actualMessageCaptor.getValue().getData()).isEqualTo(expectedMsgData); + assertThat(actualMessageCaptor.getValue().getMetaData()).isEqualTo(msgMetaData); + } + @Test public void givenValidMsgAndFetchToMetaData_whenOnMsg_thenShouldTellSuccessAndFetchToMetaData() throws TbNodeException, ExecutionException, InterruptedException { // GIVEN From fee8aa359a126cedddf0814f7fbf1cfbaecb483e Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 4 Jul 2023 18:18:23 +0300 Subject: [PATCH 019/166] refactoring --- .../server/controller/DeviceController.java | 3 +- .../src/main/resources/thingsboard.yml | 16 ++ .../server/dao/device/DeviceService.java | 3 +- .../DeviceConnectivityConfiguration.java | 9 + .../server/dao/device/DeviceServiceImpl.java | 176 ++++++++++++------ 5 files changed, 151 insertions(+), 56 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index e473163642..100fa8234c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -80,6 +80,7 @@ import javax.servlet.http.HttpServletRequest; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -168,7 +169,7 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device/{deviceId}/commands", method = RequestMethod.GET) @ResponseBody - public List getDevicePublishTelemetryCommands(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + public Map getDevicePublishTelemetryCommands(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException { checkParameter(DEVICE_ID, strDeviceId); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index e7fbbd2a3d..1c044daa74 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -775,6 +775,10 @@ transport: worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}" max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}" so_keep_alive: "${NETTY_SO_KEEPALIVE:false}" + # Mqtt device connectivity host to publish telemetry + device_connectivity_host: "${MQTT_DEVICE_CONNECTIVITY_HOST:localhost}" + # Mqtt device connectivity port to publish telemetry + device_connectivity_port: "${MQTT_DEVICE_CONNECTIVITY_PORT:1883}" # MQTT SSL configuration ssl: # Enable/disable SSL support @@ -785,6 +789,10 @@ transport: bind_port: "${MQTT_SSL_BIND_PORT:8883}" # SSL protocol: See https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#sslcontext-algorithms protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}" + # Mqtt ssl device connectivity host to publish telemetry + device_connectivity_host: "${MQTT_DEVICE_CONNECTIVITY_HOST:localhost}" + # Mqtt ssl device connectivity port to publish telemetry + device_connectivity_port: "${MQTT_DEVICE_CONNECTIVITY_PORT:8883}" # Server SSL credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) @@ -821,6 +829,10 @@ transport: piggyback_timeout: "${COAP_PIGGYBACK_TIMEOUT:500}" psm_activity_timer: "${COAP_PSM_ACTIVITY_TIMER:10000}" paging_transmission_window: "${COAP_PAGING_TRANSMISSION_WINDOW:10000}" + # Coap device connectivity host to publish telemetry + device_connectivity_host: "${COAP_DEVICE_CONNECTIVITY_HOST:localhost}" + # Coap device connectivity port to publish telemetry + device_connectivity_port: "${COAP_DEVICE_CONNECTIVITY_PORT:5683}" dtls: # Enable/disable DTLS 1.2 support enabled: "${COAP_DTLS_ENABLED:false}" @@ -830,6 +842,10 @@ transport: bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}" # CoAP DTLS bind port bind_port: "${COAP_DTLS_BIND_PORT:5684}" + # Coap DTLS device connectivity host to publish telemetry + device_connectivity_host: "${COAP_DEVICE_CONNECTIVITY_HOST:localhost}" + # Coap DTLS device connectivity port to publish telemetry + device_connectivity_port: "${COAP_DEVICE_CONNECTIVITY_PORT:5684}" # Server DTLS credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index 79f4781936..72c6a8852c 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -38,13 +38,14 @@ import org.thingsboard.server.dao.entity.EntityDaoService; import java.net.URISyntaxException; import java.util.List; +import java.util.Map; import java.util.UUID; public interface DeviceService extends EntityDaoService { DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId); - List findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException; + Map findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException; Device findDeviceById(TenantId tenantId, DeviceId deviceId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java new file mode 100644 index 0000000000..f156729cbc --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java @@ -0,0 +1,9 @@ +package org.thingsboard.server.dao.device; + +import lombok.Data; + +@Data +public class DeviceConnectivityConfiguration { + private String deviceConnectivityHost; + private Integer deviceConnectivityPort; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 82d380056b..cca89742e1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -20,6 +20,9 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; @@ -51,6 +54,7 @@ import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfigu import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.EfentoCoapDeviceTypeConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; @@ -79,11 +83,11 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; -import java.net.URI; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -124,6 +128,46 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException { + public Map findDevicePublishTelemetryCommands(String baseUrl, Device device) { DeviceId deviceId = device.getId(); log.trace("Executing findDevicePublishTelemetryCommands [{}]", deviceId); validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); - String hostname = new URI(baseUrl).getHost(); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); - DeviceCredentialsType credentialsType = deviceCredentials.getCredentialsType(); + DeviceCredentials creds = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); + DeviceCredentialsType credentialsType = creds.getCredentialsType(); DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); + DeviceTransportType transportType = deviceProfile.getTransportType(); + + Map commands = new HashMap<>(); - ArrayList commands = new ArrayList<>(); - switch (deviceProfile.getTransportType()) { + switch (transportType) { case DEFAULT: - switch (credentialsType) { + switch (credentialsType) { case ACCESS_TOKEN: - commands.add(getMqttAccessTokenCommand(hostname, deviceCredentials) + " -m " + PAYLOAD); - commands.add(getHttpAccessTokenCommand(baseUrl, deviceCredentials)); - commands.add("echo -n " + PAYLOAD + " | " + getCoapAccessTokenCommand(hostname, deviceCredentials) + " -f-"); - break; + commands.put("http", getHttpPublishCommand(baseUrl, creds)); + commands.put("mqtt", getMqttPublishCommand(mqttProperties.getDeviceConnectivityHost(), mqttProperties.getDeviceConnectivityPort(), creds)); + commands.put("mqtts", getMqttPublishCommand(mqttsProperties.getDeviceConnectivityHost(), mqttsProperties.getDeviceConnectivityPort(), creds)); + commands.put("coap", getCoapPublishCommand(coapProperties.getDeviceConnectivityHost(), coapProperties.getDeviceConnectivityPort(), creds)); + commands.put("coaps", getCoapPublishCommand(coapsProperties.getDeviceConnectivityHost(), coapsProperties.getDeviceConnectivityPort(), creds)); break; case MQTT_BASIC: - commands.add(getMqttBasicPublishCommand(hostname, deviceCredentials) + " -m " + PAYLOAD); + commands.put("mqtt", getMqttPublishCommand(mqttProperties.getDeviceConnectivityHost(), mqttProperties.getDeviceConnectivityPort(), creds)); + commands.put("mqtts", getMqttPublishCommand(mqttsProperties.getDeviceConnectivityHost(), mqttsProperties.getDeviceConnectivityPort(), creds)); break; case X509_CERTIFICATE: - commands.add(getMqttX509Command(hostname) + " -m " + PAYLOAD); + commands.put("mqtt", getMqttPublishCommand(mqttProperties.getDeviceConnectivityHost(), mqttProperties.getDeviceConnectivityPort(), creds)); + commands.put("mqtts", getMqttPublishCommand(mqttsProperties.getDeviceConnectivityHost(), mqttsProperties.getDeviceConnectivityPort(), creds)); + commands.put("coap", getCoapPublishCommand(coapProperties.getDeviceConnectivityHost(), coapProperties.getDeviceConnectivityPort(), creds)); + commands.put("coaps", getCoapPublishCommand(coapsProperties.getDeviceConnectivityHost(), coapsProperties.getDeviceConnectivityPort(), creds)); break; } break; case MQTT: MqttDeviceProfileTransportConfiguration transportConfiguration = (MqttDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + String topicName = transportConfiguration.getDeviceTelemetryTopic(); TransportPayloadType payloadType = transportConfiguration.getTransportPayloadTypeConfiguration().getTransportPayloadType(); String payload = (payloadType == TransportPayloadType.PROTOBUF) ? " -f protobufFileName" : " -m " + PAYLOAD; - switch (credentialsType) { - case ACCESS_TOKEN: - commands.add(getMqttAccessTokenCommand(hostname, deviceCredentials) + payload); - break; - case MQTT_BASIC: - commands.add(getMqttBasicPublishCommand(hostname, deviceCredentials) + payload); - break; - case X509_CERTIFICATE: - commands.add(getMqttX509Command(hostname) + payload); - break; - } + + commands.put("mqtt", getMqttPublishCommand(mqttProperties.getDeviceConnectivityHost(), mqttProperties.getDeviceConnectivityPort(), + topicName, creds, payload)); + commands.put("mqtts", getMqttPublishCommand(mqttProperties.getDeviceConnectivityHost(), mqttProperties.getDeviceConnectivityPort(), + topicName, creds, payload)); break; case COAP: CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); CoapDeviceTypeConfiguration coapConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); if (coapConfiguration instanceof DefaultCoapDeviceTypeConfiguration) { - DefaultCoapDeviceTypeConfiguration configuration = - (DefaultCoapDeviceTypeConfiguration) coapTransportConfiguration.getCoapDeviceTypeConfiguration(); - TransportPayloadType transportPayloadType = configuration.getTransportPayloadTypeConfiguration().getTransportPayloadType(); - String payloadExample = (transportPayloadType == TransportPayloadType.PROTOBUF) ? " -t binary -f protobufFileName" : " -t json -f jsonFileName"; - commands.add(getCoapAccessTokenCommand(hostname, deviceCredentials) + payloadExample); + commands.put("coap", getCoapPublishCommand(coapProperties.getDeviceConnectivityHost(), coapProperties.getDeviceConnectivityPort(), creds)); + commands.put("coaps", getCoapPublishCommand(coapsProperties.getDeviceConnectivityHost(), coapsProperties.getDeviceConnectivityPort(), creds)); + } else if (coapConfiguration instanceof EfentoCoapDeviceTypeConfiguration) { + commands.put("coap", "Not supported"); + commands.put("coaps", "Not supported"); } break; + default: + commands.put(transportType.name(), "Not supported"); } return commands; } @@ -752,36 +799,57 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Tue, 4 Jul 2023 19:04:54 +0300 Subject: [PATCH 020/166] added tests for Geofencing filter node --- .../thingsboard/server/common/msg/TbMsg.java | 2 + .../engine/geo/AbstractGeofencingNode.java | 10 +- .../thingsboard/rule/engine/geo/GeoUtil.java | 4 +- .../filter/TbAssetTypeSwitchNodeTest.java | 5 +- .../filter/TbCheckAlarmStatusNodeTest.java | 3 +- .../engine/filter/TbCheckMessageNodeTest.java | 6 +- .../filter/TbCheckRelationNodeTest.java | 5 +- .../filter/TbDeviceTypeSwitchNodeTest.java | 2 +- .../engine/filter/TbJsFilterNodeTest.java | 6 +- .../filter/TbMsgTypeFilterNodeTest.java | 2 +- .../filter/TbMsgTypeSwitchNodeTest.java | 4 +- .../TbOriginatorTypeFilterNodeTest.java | 2 +- .../TbOriginatorTypeSwitchNodeTest.java | 4 +- .../{TbGeoUtilTest.java => GeoUtilTest.java} | 2 +- .../geo/TbGpsGeofencingFilterNodeTest.java | 460 ++++++++++++++++++ 15 files changed, 482 insertions(+), 35 deletions(-) rename rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/{TbGeoUtilTest.java => GeoUtilTest.java} (99%) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index 17bc7c0a8f..9848046b11 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -42,6 +42,8 @@ import java.util.UUID; @Slf4j public final class TbMsg implements Serializable { + public static final String EMPTY = "{}"; + private final String queueName; private final UUID id; private final long ts; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java index 1f1f1fd132..b263518c41 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java @@ -48,14 +48,14 @@ public abstract class AbstractGeofencingNode getConfigClazz(); protected boolean checkMatches(TbMsg msg) throws TbNodeException { - JsonElement msgDataElement = new JsonParser().parse(msg.getData()); + JsonElement msgDataElement = JsonParser.parseString(msg.getData()); if (!msgDataElement.isJsonObject()) { - throw new TbNodeException("Incoming Message is not a valid JSON object"); + throw new TbNodeException("Incoming Message is not a valid JSON object!"); } JsonObject msgDataObj = msgDataElement.getAsJsonObject(); double latitude = getValueFromMessageByName(msg, msgDataObj, config.getLatitudeKeyName()); double longitude = getValueFromMessageByName(msg, msgDataObj, config.getLongitudeKeyName()); - List perimeters = getPerimeters(msg, msgDataObj); + List perimeters = getPerimeters(msg); boolean matches = false; for (Perimeter perimeter : perimeters) { if (checkMatches(perimeter, latitude, longitude)) { @@ -74,11 +74,11 @@ public abstract class AbstractGeofencingNode getPerimeters(TbMsg msg, JsonObject msgDataObj) throws TbNodeException { + protected List getPerimeters(TbMsg msg) throws TbNodeException { if (config.isFetchPerimeterInfoFromMessageMetadata()) { if (StringUtils.isEmpty(config.getPerimeterKeyName())) { // Old configuration before "perimeterKeyName" was introduced diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java index 519a4274c1..4467dfe30b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java @@ -45,8 +45,6 @@ public class GeoUtil { private static final SpatialContext distCtx = SpatialContext.GEO; private static final JtsSpatialContext jtsCtx; - private static final JsonParser JSON_PARSER = new JsonParser(); - static { JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.normWrapLongitude = true; @@ -64,7 +62,7 @@ public class GeoUtil { throw new RuntimeException("Polygon string can't be empty or null!"); } - JsonArray polygonsJson = normalizePolygonsJson(JSON_PARSER.parse(polygonInString).getAsJsonArray()); + JsonArray polygonsJson = normalizePolygonsJson(JsonParser.parseString(polygonInString).getAsJsonArray()); List polygons = buildPolygonsFromJson(polygonsJson); Set holes = extractHolesFrom(polygons); polygons.removeIf(holes::contains); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java index a6b2433df4..772d278ee8 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java @@ -50,9 +50,6 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R class TbAssetTypeSwitchNodeTest { - private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); - private static final String EMPTY_DATA = "{}"; - private AssetId assetId; private AssetId assetIdDeleted; private TbContext ctx; @@ -121,7 +118,7 @@ class TbAssetTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, EMPTY_METADATA, EMPTY_DATA, callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java index a794b96620..7bb8365895 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java @@ -52,7 +52,6 @@ class TbCheckAlarmStatusNodeTest { private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); private static final AlarmId ALARM_ID = new AlarmId(UUID.randomUUID()); private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); - private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); private TbCheckAlarmStatusNode node; @@ -160,7 +159,7 @@ class TbCheckAlarmStatusNodeTest { } private TbMsg getTbMsg(String msgData) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, EMPTY_METADATA, msgData); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, TbMsgMetaData.EMPTY, msgData); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java index 23d6711088..8926f36054 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java @@ -46,9 +46,7 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R class TbCheckMessageNodeTest { private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); - private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); - private static final String EMPTY_DATA = "{}"; - private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, EMPTY_METADATA, EMPTY_DATA); + private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY); private TbCheckMessageNode node; @@ -195,7 +193,7 @@ class TbCheckMessageNodeTest { } private TbMsg getTbMsg(boolean emptyData) { - String data = emptyData ? EMPTY_DATA : "{\"temperature-0\": 25}"; + String data = emptyData ? TbMsg.EMPTY : "{\"temperature-0\": 25}"; var metadata = new TbMsgMetaData(); metadata.putValue(DEVICE_NAME, "Test Device"); metadata.putValue(DEVICE_TYPE, DEFAULT_DEVICE_TYPE); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java index 59b2b823bc..926d3b654b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java @@ -26,7 +26,6 @@ import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -62,9 +61,7 @@ class TbCheckRelationNodeTest { private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); private static final DeviceId ORIGINATOR_ID = new DeviceId(UUID.randomUUID()); private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); - private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); - private static final String EMPTY_DATA = "{}"; - private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), ORIGINATOR_ID, EMPTY_METADATA, EMPTY_DATA); + private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), ORIGINATOR_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY); private TbCheckRelationNode node; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java index ef76787f94..3fe2e44f5d 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java @@ -118,6 +118,6 @@ class TbDeviceTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}", callback); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index b49dd4aac8..2f75bbfe1b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -59,7 +59,7 @@ public class TbJsFilterNodeTest { @Test public void falseEvaluationDoNotSendMsg() throws TbNodeException { initWithScript(); - TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, new TbMsgMetaData(), TbMsgDataType.JSON, TbMsg.EMPTY, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(false)); node.onMsg(ctx, msg); @@ -71,7 +71,7 @@ public class TbJsFilterNodeTest { public void exceptionInJsThrowsException() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, TbMsg.EMPTY, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFailedFuture(new ScriptException("error"))); @@ -83,7 +83,7 @@ public class TbJsFilterNodeTest { public void metadataConditionCanBeTrue() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, TbMsg.EMPTY, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java index 7814c82662..e79e2b77eb 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java @@ -97,7 +97,7 @@ class TbMsgTypeFilterNodeTest { } private TbMsg getTbMsg(EntityId entityId, TbMsgType msgType) { - return TbMsg.newMsg(msgType.name(), entityId, new TbMsgMetaData(), "{}"); + return TbMsg.newMsg(msgType.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java index d43d309cda..cd4e21182f 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java @@ -40,8 +40,6 @@ import static org.mockito.Mockito.verify; class TbMsgTypeSwitchNodeTest { private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); - private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); - private static final String EMPTY_DATA = "{}"; private TbMsgTypeSwitchNode node; @@ -90,7 +88,7 @@ class TbMsgTypeSwitchNodeTest { } private TbMsg getTbMsg(TbMsgType msgType) { - return TbMsg.newMsg(msgType.name(), DEVICE_ID, EMPTY_METADATA, EMPTY_DATA); + return TbMsg.newMsg(msgType.name(), DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java index 86ba9d1fd0..852552537f 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java @@ -96,7 +96,7 @@ class TbOriginatorTypeFilterNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(), "{}"); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java index 28d8b55264..64eb40fc41 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java @@ -42,8 +42,6 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R class TbOriginatorTypeSwitchNodeTest { private static final UUID RANDOM_UUID = UUID.randomUUID(); - private static final TbMsgMetaData EMPTY_METADATA = new TbMsgMetaData(); - private static final String EMPTY_DATA = "{}"; private TbOriginatorTypeSwitchNode node; @@ -92,7 +90,7 @@ class TbOriginatorTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, EMPTY_METADATA, EMPTY_DATA); + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGeoUtilTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GeoUtilTest.java similarity index 99% rename from rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGeoUtilTest.java rename to rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GeoUtilTest.java index 6a03a8f362..f53a876954 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGeoUtilTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GeoUtilTest.java @@ -21,7 +21,7 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) -public class TbGeoUtilTest { +public class GeoUtilTest { public static final String SIMPLE_RECT = "[[51.903762928405555,23.642220786948297],[44.669801219635644,41.83345155830211]]"; public static final String SIMPLE_RECT_WITH_HOLE_IN_CENTER = "[[[44.66980121963565,23.642220786948297],[44.66980121963565,41.83345155830211],[51.903762928405555,41.83345155830211],[51.903762928405555,23.642220786948297]],[[46.10464044504632,26.234282119122227],[50.8755868028522,26.25625220459488],[51.04164771375101,38.5595000692786],[45.99790855491869,38.75723083853248]]]"; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java new file mode 100644 index 0000000000..1a01dda8c2 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java @@ -0,0 +1,460 @@ +/** + * 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.rule.engine.geo; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.thingsboard.rule.engine.geo.GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER; +import static org.thingsboard.rule.engine.geo.GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; + +class TbGpsGeofencingFilterNodeTest { + + private static final double CIRCLE_RANGE = 1.0; + private static final Coordinates CIRCLE_CENTER = new Coordinates(49.0384, 31.4513); + private static final Coordinates POINT_INSIDE_CIRCLE = new Coordinates(49.0354, 31.4513); // distance from center: 0.334 km + private static final Coordinates POINT_OUTSIDE_CIRCLE = new Coordinates(49.0284, 31.4513); // distance from center: 1.112 km + + private TbContext ctx; + private TbGpsGeofencingFilterNode node; + + @BeforeEach + void setUp() { + ctx = mock(TbContext.class); + node = new TbGpsGeofencingFilterNode(); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + // Exception tests + + @Test + void givenDefaultConfig_whenOnMsg_thenExceptionInvalidMsg() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getEmptyArrayTbMsg(deviceId); + + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.onMsg(ctx, msg)); + + // THEN + assertThat(exception.getMessage()).isEqualTo("Incoming Message is not a valid JSON object!"); + } + + @Test + void givenDefaultConfig_whenOnMsg_thenExceptionMissingPerimeterDefinitionNewVersion() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, + POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.onMsg(ctx, msg)); + + // THEN + assertThat(exception.getMessage()).isEqualTo("Missing perimeter definition!"); + } + + @Test + void givenTypePolygonAndConfigWithoutPerimeterKeyName_whenOnMsg_thenExceptionMissingPerimeterDefinitionOldVersion() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterKeyName(null); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, + POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + + // WHEN + var exception = assertThrows(TbNodeException.class, () -> node.onMsg(ctx, msg)); + + // THEN + assertThat(exception.getMessage()).isEqualTo("Missing perimeter definition!"); + } + + // Polygon tests + + @Test + void givenTypePolygonAndConfigWithoutPerimeterKeyName_whenOnMsg_thenTrue() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterKeyName(null); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForOldVersionPolygonPerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypePolygonAndConfigWithoutPerimeterKeyName_whenOnMsg_thenFalse() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterKeyName(null); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForOldVersionPolygonPerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenDefaultConfig_whenOnMsg_thenTrue() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForNewVersionPolygonPerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenDefaultConfig_whenOnMsg_thenFalse() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForNewVersionPolygonPerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypePolygonAndConfigWithPolygonDefined_whenOnMsg_thenTrue() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setFetchPerimeterInfoFromMessageMetadata(false); + config.setPolygonsDefinition(GeoUtilTest.SIMPLE_RECT); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, + POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypePolygonAndConfigWithPolygonDefined_whenOnMsg_thenFalse() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setFetchPerimeterInfoFromMessageMetadata(false); + config.setPolygonsDefinition(GeoUtilTest.SIMPLE_RECT); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, + POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + private TbMsgMetaData getMetadataForOldVersionPolygonPerimeter() { + var metadata = new TbMsgMetaData(); + metadata.putValue("perimeter", GeoUtilTest.SIMPLE_RECT); + return metadata; + } + + private TbMsgMetaData getMetadataForNewVersionPolygonPerimeter() { + var metadata = new TbMsgMetaData(); + metadata.putValue("ss_perimeter", GeoUtilTest.SIMPLE_RECT); + return metadata; + } + + // Circle tests + + @Test + void givenTypeCircleAndConfigWithoutPerimeterKeyName_whenOnMsg_thenTrue() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterKeyName(null); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForOldVersionCirclePerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_INSIDE_CIRCLE.getLatitude(), POINT_INSIDE_CIRCLE.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypeCircleAndConfigWithoutPerimeterKeyName_whenOnMsg_thenFalse() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterKeyName(null); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForOldVersionCirclePerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_OUTSIDE_CIRCLE.getLatitude(), POINT_OUTSIDE_CIRCLE.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypeCircle_whenOnMsg_thenTrue() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterType(PerimeterType.CIRCLE); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForNewVersionCirclePerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_INSIDE_CIRCLE.getLatitude(), POINT_INSIDE_CIRCLE.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypeCircle_whenOnMsg_thenFalse() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setPerimeterType(PerimeterType.CIRCLE); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForNewVersionCirclePerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + POINT_OUTSIDE_CIRCLE.getLatitude(), POINT_OUTSIDE_CIRCLE.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypeCircleAndConfigWithCircleDefined_whenOnMsg_thenTrue() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setFetchPerimeterInfoFromMessageMetadata(false); + config.setPerimeterType(PerimeterType.CIRCLE); + config.setCenterLatitude(CIRCLE_CENTER.getLatitude()); + config.setCenterLongitude(CIRCLE_CENTER.getLongitude()); + config.setRange(CIRCLE_RANGE); + config.setRangeUnit(RangeUnit.KILOMETER); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, + POINT_INSIDE_CIRCLE.getLatitude(), POINT_INSIDE_CIRCLE.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + @Test + void givenTypeCircleAndConfigWithCircleDefined_whenOnMsg_thenFalse() throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingFilterNodeConfiguration().defaultConfiguration(); + config.setFetchPerimeterInfoFromMessageMetadata(false); + config.setPerimeterType(PerimeterType.CIRCLE); + config.setCenterLatitude(CIRCLE_CENTER.getLatitude()); + config.setCenterLongitude(CIRCLE_CENTER.getLongitude()); + config.setRange(CIRCLE_RANGE); + config.setRangeUnit(RangeUnit.KILOMETER); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, + POINT_OUTSIDE_CIRCLE.getLatitude(), POINT_OUTSIDE_CIRCLE.getLongitude()); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE)); + verify(ctx, never()).tellFailure(any(), any()); + TbMsg newMsg = newMsgCaptor.getValue(); + assertThat(newMsg).isNotNull(); + assertThat(newMsg).isSameAs(msg); + } + + private TbMsgMetaData getMetadataForOldVersionCirclePerimeter() { + var metadata = new TbMsgMetaData(); + metadata.putValue("centerLatitude", String.valueOf(CIRCLE_CENTER.getLatitude())); + metadata.putValue("centerLongitude", String.valueOf(CIRCLE_CENTER.getLongitude())); + metadata.putValue("range", String.valueOf(CIRCLE_RANGE)); + metadata.putValue("rangeUnit", String.valueOf(RangeUnit.KILOMETER)); + return metadata; + } + + private TbMsgMetaData getMetadataForNewVersionCirclePerimeter() { + ObjectNode perimeter = JacksonUtil.newObjectNode(); + perimeter.put("latitude", CIRCLE_CENTER.getLatitude()); + perimeter.put("longitude", CIRCLE_CENTER.getLongitude()); + perimeter.put("radius", CIRCLE_RANGE); + perimeter.put("radiusUnit", String.valueOf(RangeUnit.KILOMETER)); + var metadata = new TbMsgMetaData(); + metadata.putValue("ss_perimeter", JacksonUtil.toString(perimeter)); + return metadata; + } + + private TbMsg getTbMsg(EntityId entityId, TbMsgMetaData metadata, double latitude, double longitude) { + String data = "{\"latitude\": " + latitude + ", \"longitude\": " + longitude + "}"; + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, metadata, data); + } + + private TbMsg getEmptyArrayTbMsg(EntityId entityId) { + return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, "[]"); + } + +} From 3baa12ce7739bfec4e66bb0c3392f64fccd0e720 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 5 Jul 2023 15:06:42 +0300 Subject: [PATCH 021/166] refactored config properties --- .../src/main/resources/thingsboard.yml | 35 ++-- .../DeviceConnectivityConfiguration.java | 24 ++- .../dao/device/DeviceConnectivityInfo.java | 26 +++ .../server/dao/device/DeviceServiceImpl.java | 168 +++++++++--------- 4 files changed, 152 insertions(+), 101 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 1c044daa74..f8ea15b2b8 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -775,10 +775,6 @@ transport: worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}" max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}" so_keep_alive: "${NETTY_SO_KEEPALIVE:false}" - # Mqtt device connectivity host to publish telemetry - device_connectivity_host: "${MQTT_DEVICE_CONNECTIVITY_HOST:localhost}" - # Mqtt device connectivity port to publish telemetry - device_connectivity_port: "${MQTT_DEVICE_CONNECTIVITY_PORT:1883}" # MQTT SSL configuration ssl: # Enable/disable SSL support @@ -789,10 +785,6 @@ transport: bind_port: "${MQTT_SSL_BIND_PORT:8883}" # SSL protocol: See https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#sslcontext-algorithms protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}" - # Mqtt ssl device connectivity host to publish telemetry - device_connectivity_host: "${MQTT_DEVICE_CONNECTIVITY_HOST:localhost}" - # Mqtt ssl device connectivity port to publish telemetry - device_connectivity_port: "${MQTT_DEVICE_CONNECTIVITY_PORT:8883}" # Server SSL credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) @@ -829,10 +821,6 @@ transport: piggyback_timeout: "${COAP_PIGGYBACK_TIMEOUT:500}" psm_activity_timer: "${COAP_PSM_ACTIVITY_TIMER:10000}" paging_transmission_window: "${COAP_PAGING_TRANSMISSION_WINDOW:10000}" - # Coap device connectivity host to publish telemetry - device_connectivity_host: "${COAP_DEVICE_CONNECTIVITY_HOST:localhost}" - # Coap device connectivity port to publish telemetry - device_connectivity_port: "${COAP_DEVICE_CONNECTIVITY_PORT:5683}" dtls: # Enable/disable DTLS 1.2 support enabled: "${COAP_DTLS_ENABLED:false}" @@ -842,10 +830,6 @@ transport: bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}" # CoAP DTLS bind port bind_port: "${COAP_DTLS_BIND_PORT:5684}" - # Coap DTLS device connectivity host to publish telemetry - device_connectivity_host: "${COAP_DEVICE_CONNECTIVITY_HOST:localhost}" - # Coap DTLS device connectivity port to publish telemetry - device_connectivity_port: "${COAP_DEVICE_CONNECTIVITY_PORT:5684}" # Server DTLS credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) @@ -994,6 +978,25 @@ transport: enabled: "${TB_TRANSPORT_STATS_ENABLED:true}" print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}" +# Device connectivity properties to publish telemetry +device: + connectivity: + http: + host: "${DEVICE_CONNECTIVITY_HTTP_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_HTTP_PORT:8080}" + mqtt: + host: "${DEVICE_CONNECTIVITY_MQTT_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_MQTT_PORT:1883}" + mqtts: + host: "${DEVICE_CONNECTIVITY_MQTTS_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_MQTTS_PORT:8883}" + coap: + host: "${DEVICE_CONNECTIVITY_COAP_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_COAP_PORT:5683}" + coaps: + host: "${DEVICE_CONNECTIVITY_COAPS_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_COAPS_PORT:5684}" + # Edges parameters edges: enabled: "${EDGES_ENABLED:true}" diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java index f156729cbc..454c795f12 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java @@ -1,9 +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.dao.device; import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import java.util.Map; + +@Configuration +@ConfigurationProperties(prefix = "device") @Data public class DeviceConnectivityConfiguration { - private String deviceConnectivityHost; - private Integer deviceConnectivityPort; + private Map connectivity; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java new file mode 100644 index 0000000000..7b477bfc42 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java @@ -0,0 +1,26 @@ +/** + * 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.dao.device; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + + +@Data +public class DeviceConnectivityInfo { + private String host; + private Integer port; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index cca89742e1..fe5ac33e73 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -20,9 +20,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; @@ -108,7 +105,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService commands.put("http", v)); + Optional.ofNullable(getMqttPublishCommand(creds)).ifPresent(v -> commands.put("mqtt", v)); + Optional.ofNullable(getMqttsPublishCommand(creds)).ifPresent(v -> commands.put("mqtts", v)); + Optional.ofNullable(getCoapPublishCommand(creds)).ifPresent(v -> commands.put("coap", v)); + Optional.ofNullable(getCoapsPublishCommand(creds)).ifPresent(v -> commands.put("coaps", v)); break; case MQTT: MqttDeviceProfileTransportConfiguration transportConfiguration = @@ -217,25 +164,22 @@ public class DeviceServiceImpl extends AbstractCachedEntityService commands.put("mqtt", v)); + Optional.ofNullable(getMqttsPublishCommand(topicName, creds, payload)).ifPresent(v -> commands.put("mqtts", v)); break; case COAP: CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); CoapDeviceTypeConfiguration coapConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); if (coapConfiguration instanceof DefaultCoapDeviceTypeConfiguration) { - commands.put("coap", getCoapPublishCommand(coapProperties.getDeviceConnectivityHost(), coapProperties.getDeviceConnectivityPort(), creds)); - commands.put("coaps", getCoapPublishCommand(coapsProperties.getDeviceConnectivityHost(), coapsProperties.getDeviceConnectivityPort(), creds)); + Optional.ofNullable(getCoapPublishCommand(creds)).ifPresent(v -> commands.put("coap", v)); + Optional.ofNullable(getCoapsPublishCommand(creds)).ifPresent(v -> commands.put("coaps", v)); } else if (coapConfiguration instanceof EfentoCoapDeviceTypeConfiguration) { - commands.put("coap", "Not supported"); - commands.put("coaps", "Not supported"); + commands.put("coap for efento", "Not supported"); } break; default: - commands.put(transportType.name(), "Not supported"); + commands.put(transportType.name(), NOT_SUPPORTED); } return commands; } @@ -800,18 +744,61 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Wed, 5 Jul 2023 15:27:52 +0300 Subject: [PATCH 022/166] minor refactoring --- application/src/main/resources/thingsboard.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f8ea15b2b8..2f58590bf6 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -981,9 +981,6 @@ transport: # Device connectivity properties to publish telemetry device: connectivity: - http: - host: "${DEVICE_CONNECTIVITY_HTTP_HOST:localhost}" - port: "${DEVICE_CONNECTIVITY_HTTP_PORT:8080}" mqtt: host: "${DEVICE_CONNECTIVITY_MQTT_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_MQTT_PORT:1883}" From 47929ef78442808c32b88fa627426b5f98367a7e Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 6 Jul 2023 12:20:23 +0300 Subject: [PATCH 023/166] replaced newMsg and trasformMsg with new methods that uses TbMsgType && mark old methods as deprecated && refactoring --- .../actors/ruleChain/DefaultTbContext.java | 58 +++++-- .../server/controller/RpcV2Controller.java | 4 +- .../service/action/EntityActionService.java | 2 +- .../device/DeviceProvisionServiceImpl.java | 29 ++-- .../service/edge/rpc/EdgeGrpcService.java | 15 +- .../processor/device/DeviceEdgeProcessor.java | 9 +- .../telemetry/BaseTelemetryProcessor.java | 11 +- .../DefaultTbNotificationEntityService.java | 5 +- .../rpc/DefaultTbCoreDeviceRpcService.java | 5 +- .../server/service/rpc/TbRpcService.java | 3 +- .../state/DefaultDeviceStateService.java | 21 +-- .../transport/DefaultTransportApiService.java | 4 +- .../AbstractRuleEngineControllerTest.java | 6 +- ...AbstractRuleEngineFlowIntegrationTest.java | 13 +- ...actRuleEngineLifecycleIntegrationTest.java | 7 +- .../SequentialTimeseriesPersistenceTest.java | 4 +- .../server/common/data/msg/TbMsgType.java | 32 +++- .../server/common/data/msg/TbMsgTypeTest.java | 18 +- .../thingsboard/server/common/msg/TbMsg.java | 164 +++++++++++++++++- .../service/DefaultTransportService.java | 2 +- .../rule/engine/api/TbContext.java | 32 +++- .../rule/engine/api/util/TbNodeUtilsTest.java | 11 +- .../engine/action/TbAbstractAlarmNode.java | 21 +-- .../rule/engine/action/TbClearAlarmNode.java | 2 +- .../rule/engine/action/TbCreateAlarmNode.java | 4 +- .../engine/action/TbCreateRelationNode.java | 2 +- .../rule/engine/action/TbMsgCountNode.java | 15 +- .../rule/engine/aws/sns/TbSnsNode.java | 4 +- .../rule/engine/aws/sqs/TbSqsNode.java | 17 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 13 +- .../deduplication/TbMsgDeduplicationNode.java | 8 +- .../rule/engine/delay/TbMsgDelayNode.java | 12 +- .../rule/engine/gcp/pubsub/TbPubSubNode.java | 13 +- .../rule/engine/kafka/TbKafkaNode.java | 12 +- .../rule/engine/mail/TbMsgToEmailNode.java | 11 +- .../rule/engine/mail/TbSendEmailNode.java | 8 +- .../engine/metadata/CalculateDeltaNode.java | 2 +- .../engine/metadata/TbGetTelemetryNode.java | 18 +- .../rule/engine/mqtt/TbMqttNode.java | 10 +- .../rule/engine/profile/AlarmState.java | 3 +- .../rule/engine/profile/DeviceState.java | 6 +- .../engine/profile/TbDeviceProfileNode.java | 61 ++++--- .../rule/engine/rabbitmq/TbRabbitMqNode.java | 7 +- .../rule/engine/rest/TbHttpClient.java | 16 +- .../rule/engine/rest/TbRestApiCallNode.java | 1 - .../rule/engine/rpc/TbSendRPCRequestNode.java | 5 +- .../transform/TbChangeOriginatorNode.java | 10 +- .../rule/engine/transform/TbCopyKeysNode.java | 2 +- .../engine/transform/TbDeleteKeysNode.java | 4 +- .../rule/engine/transform/TbJsonPathNode.java | 2 +- .../engine/transform/TbRenameKeysNode.java | 4 +- .../engine/transform/TbSplitArrayMsgNode.java | 4 +- .../rule/engine/action/TbAlarmNodeTest.java | 110 ++++++------ .../action/TbCreateRelationNodeTest.java | 36 ++-- .../rule/engine/action/TbLogNodeTest.java | 7 +- .../engine/edge/TbMsgPushToEdgeNodeTest.java | 25 ++- .../filter/TbAssetTypeSwitchNodeTest.java | 4 +- .../filter/TbCheckAlarmStatusNodeTest.java | 4 +- .../engine/filter/TbCheckMessageNodeTest.java | 16 +- .../filter/TbCheckRelationNodeTest.java | 4 +- .../filter/TbDeviceTypeSwitchNodeTest.java | 4 +- .../engine/filter/TbJsFilterNodeTest.java | 10 +- .../engine/filter/TbJsSwitchNodeTest.java | 3 +- .../filter/TbMsgTypeFilterNodeTest.java | 2 +- .../filter/TbMsgTypeSwitchNodeTest.java | 2 +- .../TbOriginatorTypeFilterNodeTest.java | 4 +- .../TbOriginatorTypeSwitchNodeTest.java | 4 +- .../geo/TbGpsGeofencingFilterNodeTest.java | 24 ++- .../engine/mail/TbMsgToEmailNodeTest.java | 9 +- .../rule/engine/math/TbMathNodeTest.java | 31 ++-- .../metadata/CalculateDeltaNodeTest.java | 29 ++-- .../TbFetchDeviceCredentialsNodeTest.java | 8 +- .../metadata/TbGetAttributesNodeTest.java | 5 +- .../TbGetCustomerAttributeNodeTest.java | 14 +- .../TbGetCustomerDetailsNodeTest.java | 6 +- .../TbGetOriginatorFieldsNodeTest.java | 14 +- .../TbGetRelatedAttributeNodeTest.java | 12 +- .../TbGetTenantAttributeNodeTest.java | 12 +- .../metadata/TbGetTenantDetailsNodeTest.java | 6 +- .../rule/engine/profile/DeviceStateTest.java | 24 ++- .../profile/TbDeviceProfileNodeTest.java | 118 ++++++------- .../rule/engine/rest/TbHttpClientTest.java | 13 +- .../engine/rest/TbRestApiCallNodeTest.java | 23 +-- .../engine/rpc/TbSendRPCReplyNodeTest.java | 4 +- .../TbMsgDeleteAttributesNodeTest.java | 17 +- .../transform/TbChangeOriginatorNodeTest.java | 17 +- .../engine/transform/TbCopyKeysNodeTest.java | 4 +- .../transform/TbDeleteKeysNodeTest.java | 4 +- .../engine/transform/TbJsonPathNodeTest.java | 4 +- .../transform/TbMsgDeduplicationNodeTest.java | 15 +- .../transform/TbRenameKeysNodeTest.java | 4 +- .../transform/TbSplitArrayMsgNodeTest.java | 4 +- .../transform/TbTransformMsgNodeTest.java | 7 +- 93 files changed, 794 insertions(+), 621 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index f9d1940714..dc03a97254 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -59,6 +59,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -345,8 +346,33 @@ class DefaultTbContext implements TbContext { return TbMsg.transformMsg(origMsg, type, originator, metaData, data); } + @Override + public TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) { + return newMsg(queueName, type, originator, null, metaData, data); + } + + @Override + public TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { + return TbMsg.newMsg(queueName, type, originator, customerId, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId()); + } + + @Override + public TbMsg transformMsg(TbMsg origMsg, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) { + return TbMsg.transformMsg(origMsg, type, originator, metaData, data); + } + + @Override + public TbMsg transformMsg(TbMsg origMsg, TbMsgMetaData metaData, String data) { + return TbMsg.transformMsg(origMsg, metaData, data); + } + + @Override + public TbMsg transformMsgOriginator(TbMsg origMsg, EntityId originator) { + return TbMsg.transformMsgOriginator(origMsg, originator); + } + public TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId) { - return entityActionMsg(customer, customer.getId(), ruleNodeId, ENTITY_CREATED.name()); + return entityActionMsg(customer, customer.getId(), ruleNodeId, ENTITY_CREATED); } public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) { @@ -354,7 +380,7 @@ class DefaultTbContext implements TbContext { if (device.getDeviceProfileId() != null) { deviceProfile = mainCtx.getDeviceProfileCache().find(device.getDeviceProfileId()); } - return entityActionMsg(device, device.getId(), ruleNodeId, ENTITY_CREATED.name(), deviceProfile); + return entityActionMsg(device, device.getId(), ruleNodeId, ENTITY_CREATED, deviceProfile); } public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) { @@ -362,10 +388,10 @@ class DefaultTbContext implements TbContext { if (asset.getAssetProfileId() != null) { assetProfile = mainCtx.getAssetProfileCache().find(asset.getAssetProfileId()); } - return entityActionMsg(asset, asset.getId(), ruleNodeId, ENTITY_CREATED.name(), assetProfile); + return entityActionMsg(asset, asset.getId(), ruleNodeId, ENTITY_CREATED, assetProfile); } - public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) { + public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, TbMsgType actionMsgType) { HasRuleEngineProfile profile = null; if (EntityType.DEVICE.equals(alarm.getOriginator().getEntityType())) { DeviceId deviceId = new DeviceId(alarm.getOriginator().getId()); @@ -374,7 +400,7 @@ class DefaultTbContext implements TbContext { AssetId assetId = new AssetId(alarm.getOriginator().getId()); profile = mainCtx.getAssetProfileCache().get(getTenantId(), assetId); } - return entityActionMsg(alarm, alarm.getOriginator(), ruleNodeId, action, profile); + return entityActionMsg(alarm, alarm.getOriginator(), ruleNodeId, actionMsgType, profile); } public TbMsg attributesUpdatedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List attributes) { @@ -382,7 +408,7 @@ class DefaultTbContext implements TbContext { if (attributes != null) { attributes.forEach(attributeKvEntry -> JacksonUtil.addKvEntry(entityNode, attributeKvEntry)); } - return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_UPDATED.name(), JacksonUtil.toString(entityNode)); + return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_UPDATED, JacksonUtil.toString(entityNode)); } public TbMsg attributesDeletedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List keys) { @@ -391,10 +417,10 @@ class DefaultTbContext implements TbContext { if (keys != null) { keys.forEach(attrsArrayNode::add); } - return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_DELETED.name(), JacksonUtil.toString(entityNode)); + return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_DELETED, JacksonUtil.toString(entityNode)); } - private TbMsg attributesActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, String action, String msgData) { + private TbMsg attributesActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, TbMsgType actionMsgType, String msgData) { TbMsgMetaData tbMsgMetaData = getActionMetaData(ruleNodeId); tbMsgMetaData.putValue("scope", scope); HasRuleEngineProfile profile = null; @@ -405,7 +431,7 @@ class DefaultTbContext implements TbContext { AssetId assetId = new AssetId(originator.getId()); profile = mainCtx.getAssetProfileCache().get(getTenantId(), assetId); } - return entityActionMsg(originator, tbMsgMetaData, msgData, action, profile); + return entityActionMsg(originator, tbMsgMetaData, msgData, actionMsgType, profile); } @Override @@ -413,26 +439,26 @@ class DefaultTbContext implements TbContext { mainCtx.getClusterService().onEdgeEventUpdate(tenantId, edgeId); } - public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action) { - return entityActionMsg(entity, id, ruleNodeId, action, null); + public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, TbMsgType actionMsgType) { + return entityActionMsg(entity, id, ruleNodeId, actionMsgType, null); } - public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action, K profile) { + public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, TbMsgType actionMsgType, K profile) { try { - return entityActionMsg(id, getActionMetaData(ruleNodeId), JacksonUtil.toString(JacksonUtil.valueToTree(entity)), action, profile); + return entityActionMsg(id, getActionMetaData(ruleNodeId), JacksonUtil.toString(JacksonUtil.valueToTree(entity)), actionMsgType, profile); } catch (IllegalArgumentException e) { - throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e); + throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + actionMsgType.name() + " msg: " + e); } } - private TbMsg entityActionMsg(I id, TbMsgMetaData msgMetaData, String msgData, String action, K profile) { + private TbMsg entityActionMsg(I id, TbMsgMetaData msgMetaData, String msgData, TbMsgType actionMsgType, K profile) { String defaultQueueName = null; RuleChainId defaultRuleChainId = null; if (profile != null) { defaultQueueName = profile.getDefaultQueueName(); defaultRuleChainId = profile.getDefaultRuleChainId(); } - return TbMsg.newMsg(defaultQueueName, action, id, msgMetaData, msgData, defaultRuleChainId, null); + return TbMsg.newMsg(defaultQueueName, actionMsgType, id, msgMetaData, msgData, defaultRuleChainId, null); } @Override diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java index d65f87497d..451a5b7dc0 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.RpcId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rpc.Rpc; @@ -52,7 +53,6 @@ import org.thingsboard.server.service.security.permission.Operation; import javax.annotation.Nullable; import java.util.UUID; -import static org.thingsboard.server.common.data.msg.TbMsgType.RPC_DELETED; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END; @@ -239,7 +239,7 @@ public class RpcV2Controller extends AbstractRpcController { rpcService.deleteRpc(getTenantId(), rpcId); rpc.setStatus(RpcStatus.DELETED); - TbMsg msg = TbMsg.newMsg(RPC_DELETED.name(), rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); + TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_DELETED, rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); tbClusterService.pushMsgToRuleEngine(getTenantId(), rpc.getDeviceId(), msg, null); } } diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java index cea4a6922a..6c855a89c9 100644 --- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java +++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java @@ -171,7 +171,7 @@ public class EntityActionService { if (tenantId != null && !tenantId.isSysTenantId()) { processNotificationRules(tenantId, entityId, entity, actionType, user, additionalInfo); } - TbMsg tbMsg = TbMsg.newMsg(msgType.get().name(), entityId, customerId, metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); + TbMsg tbMsg = TbMsg.newMsg(msgType.get(), entityId, customerId, metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null); } catch (Exception e) { log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e); diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index d5614c2193..763ce01116 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; @@ -35,6 +36,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.msg.TbMsg; @@ -68,11 +70,6 @@ import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; -import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_FAILURE; -import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_SUCCESS; - @Service @Slf4j @@ -166,7 +163,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { if (targetProfile.getProfileData().getProvisionConfiguration().getProvisionDeviceSecret().equals(provisionRequestSecret)) { if (targetDevice != null) { log.warn("[{}] The device is present and could not be provisioned once more!", targetDevice.getName()); - notify(targetDevice, provisionRequest, PROVISION_FAILURE.name(), false); + notify(targetDevice, provisionRequest, TbMsgType.PROVISION_FAILURE, false); throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } else { return createDevice(provisionRequest, targetProfile); @@ -192,13 +189,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private ProvisionResponse processProvision(Device device, ProvisionRequest provisionRequest) { try { Optional provisionState = attributesService.find(device.getTenantId(), device.getId(), - SERVER_SCOPE, DEVICE_PROVISION_STATE).get(); + DataConstants.SERVER_SCOPE, DEVICE_PROVISION_STATE).get(); if (provisionState != null && provisionState.isPresent() && !provisionState.get().getValueAsString().equals(PROVISIONED_STATE)) { - notify(device, provisionRequest, PROVISION_FAILURE.name(), false); + notify(device, provisionRequest, TbMsgType.PROVISION_FAILURE, false); throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } else { saveProvisionStateAttribute(device).get(); - notify(device, provisionRequest, PROVISION_SUCCESS.name(), true); + notify(device, provisionRequest, TbMsgType.PROVISION_SUCCESS, true); } } catch (InterruptedException | ExecutionException e) { throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); @@ -210,7 +207,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return processCreateDevice(provisionRequest, profile); } - private void notify(Device device, ProvisionRequest provisionRequest, String type, boolean success) { + private void notify(Device device, ProvisionRequest provisionRequest, TbMsgType type, boolean success) { pushProvisionEventToRuleEngine(provisionRequest, device, type); logAction(device.getTenantId(), device.getCustomerId(), device, success, provisionRequest); } @@ -226,14 +223,14 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { clusterService.onDeviceUpdated(savedDevice, null); saveProvisionStateAttribute(savedDevice).get(); pushDeviceCreatedEventToRuleEngine(savedDevice); - notify(savedDevice, provisionRequest, PROVISION_SUCCESS.name(), true); + notify(savedDevice, provisionRequest, TbMsgType.PROVISION_SUCCESS, true); return new ProvisionResponse(getDeviceCredentials(savedDevice), ProvisionResponseStatus.SUCCESS); } catch (Exception e) { log.warn("[{}] Error during device creation from provision request: [{}]", provisionRequest.getDeviceName(), provisionRequest, e); Device device = deviceService.findDeviceByTenantIdAndName(profile.getTenantId(), provisionRequest.getDeviceName()); if (device != null) { - notify(device, provisionRequest, PROVISION_FAILURE.name(), false); + notify(device, provisionRequest, TbMsgType.PROVISION_FAILURE, false); } throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } @@ -248,7 +245,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { } private ListenableFuture> saveProvisionStateAttribute(Device device) { - return attributesService.save(device.getTenantId(), device.getId(), SERVER_SCOPE, + return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE), System.currentTimeMillis()))); } @@ -257,7 +254,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { return deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); } - private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) { + private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, TbMsgType type) { try { JsonNode entityNode = JacksonUtil.valueToTree(request); TbMsg msg = TbMsg.newMsg(type, device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.toString(entityNode)); @@ -270,10 +267,10 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private void pushDeviceCreatedEventToRuleEngine(Device device) { try { ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); - TbMsg msg = TbMsg.newMsg(ENTITY_CREATED.name(), device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); + TbMsg msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); sendToRuleEngine(device.getTenantId(), msg, null); } catch (JsonProcessingException | IllegalArgumentException e) { - log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), ENTITY_CREATED.name(), e); + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), TbMsgType.ENTITY_CREATED.name(), e); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index 233dcba2cc..73f968e05d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -71,10 +72,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; - @Service @Slf4j @ConditionalOnProperty(prefix = "edges", value = "enabled", havingValue = "true") @@ -278,7 +275,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, true); long lastConnectTs = System.currentTimeMillis(); save(edgeId, DefaultDeviceStateService.LAST_CONNECT_TIME, lastConnectTs); - pushRuleEngineMessage(edgeGrpcSession.getEdge().getTenantId(), edgeId, lastConnectTs, CONNECT_EVENT.name()); + pushRuleEngineMessage(edgeGrpcSession.getEdge().getTenantId(), edgeId, lastConnectTs, TbMsgType.CONNECT_EVENT); cancelScheduleEdgeEventsCheck(edgeId); scheduleEdgeEventsCheck(edgeGrpcSession); } @@ -395,7 +392,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, false); long lastDisconnectTs = System.currentTimeMillis(); save(edgeId, DefaultDeviceStateService.LAST_DISCONNECT_TIME, lastDisconnectTs); - pushRuleEngineMessage(toRemove.getEdge().getTenantId(), edgeId, lastDisconnectTs, DISCONNECT_EVENT.name()); + pushRuleEngineMessage(toRemove.getEdge().getTenantId(), edgeId, lastDisconnectTs, TbMsgType.DISCONNECT_EVENT); cancelScheduleEdgeEventsCheck(edgeId); } else { log.debug("[{}] edge session [{}] is not available anymore, nothing to remove. most probably this session is already outdated!", edgeId, sessionId); @@ -448,10 +445,10 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i } } - private void pushRuleEngineMessage(TenantId tenantId, EdgeId edgeId, long ts, String msgType) { + private void pushRuleEngineMessage(TenantId tenantId, EdgeId edgeId, long ts, TbMsgType msgType) { try { ObjectNode edgeState = JacksonUtil.newObjectNode(); - if (msgType.equals(CONNECT_EVENT.name())) { + if (msgType.equals(TbMsgType.CONNECT_EVENT)) { edgeState.put(DefaultDeviceStateService.ACTIVITY_STATE, true); edgeState.put(DefaultDeviceStateService.LAST_CONNECT_TIME, ts); } else { @@ -461,7 +458,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i String data = JacksonUtil.toString(edgeState); TbMsgMetaData md = new TbMsgMetaData(); if (!persistToTelemetry) { - md.putValue(DataConstants.SCOPE, SERVER_SCOPE); + md.putValue(DataConstants.SCOPE, DataConstants.SERVER_SCOPE); } TbMsg tbMsg = TbMsg.newMsg(msgType, edgeId, md, TbMsgDataType.JSON, data); clusterService.pushMsgToRuleEngine(tenantId, edgeId, tbMsg, null); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java index 49f2e0ead2..67194cb618 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java @@ -62,9 +62,6 @@ import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; import java.util.UUID; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; -import static org.thingsboard.server.common.data.msg.TbMsgType.TO_SERVER_RPC_REQUEST; - @Component @Slf4j @TbCoreComponent @@ -127,7 +124,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { try { Device device = deviceService.findDeviceById(tenantId, deviceId); ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); - TbMsg tbMsg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, device.getCustomerId(), + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, device.getCustomerId(), getActionTbMsgMetaData(edge, device.getCustomerId()), TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { @Override @@ -141,7 +138,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { } }); } catch (JsonProcessingException | IllegalArgumentException e) { - log.warn("[{}] Failed to push device action to rule engine: {}", deviceId, ENTITY_CREATED.name(), e); + log.warn("[{}] Failed to push device action to rule engine: {}", deviceId, TbMsgType.ENTITY_CREATED.name(), e); } } @@ -219,7 +216,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { ObjectNode data = JacksonUtil.newObjectNode(); data.put("method", deviceRpcCallMsg.getRequestMsg().getMethod()); data.put("params", deviceRpcCallMsg.getRequestMsg().getParams()); - TbMsg tbMsg = TbMsg.newMsg(TO_SERVER_RPC_REQUEST.name(), deviceId, null, metaData, + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.TO_SERVER_RPC_REQUEST, deviceId, null, metaData, TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(data)); tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { @Override diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index c55a931194..5942628bf3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -50,6 +50,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -72,10 +73,6 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; -import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; - @Slf4j public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { @@ -187,7 +184,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); metaData.putValue("ts", tsKv.getTs() + ""); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), POST_TELEMETRY_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.POST_TELEMETRY_REQUEST, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { @@ -231,7 +228,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { SettableFuture futureToSet = SettableFuture.create(); JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), POST_ATTRIBUTES_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { @@ -260,7 +257,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { @Override public void onSuccess(@Nullable Void tmp) { var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), ATTRIBUTES_UPDATED.name(), entityId, + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.ATTRIBUTES_UPDATED, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java index 719158403c..68b9cdecbb 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.rule.RuleChain; @@ -52,8 +53,6 @@ import org.thingsboard.server.service.gateway_device.GatewayNotificationsService import java.util.List; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED_FROM_TENANT; - @Slf4j @Service @RequiredArgsConstructor @@ -287,7 +286,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) { String data = JacksonUtil.toString(JacksonUtil.valueToTree(assignedDevice)); if (data != null) { - TbMsg tbMsg = TbMsg.newMsg(ENTITY_ASSIGNED_FROM_TENANT.name(), assignedDevice.getId(), + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), assignedDevice.getCustomerId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null); } diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java index 2ec8f09bd0..8ea7208c55 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java @@ -26,6 +26,7 @@ import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -48,8 +49,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import static org.thingsboard.server.common.data.msg.TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE; - /** * Created by ashvayka on 27.03.18. */ @@ -183,7 +182,7 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { entityNode.put(DataConstants.ADDITIONAL_INFO, msg.getAdditionalInfo()); try { - TbMsg tbMsg = TbMsg.newMsg(RPC_CALL_FROM_SERVER_TO_DEVICE.name(), msg.getDeviceId(), Optional.ofNullable(currentUser).map(User::getCustomerId).orElse(null), metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), Optional.ofNullable(currentUser).map(User::getCustomerId).orElse(null), metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); clusterService.pushMsgToRuleEngine(msg.getTenantId(), msg.getDeviceId(), tbMsg, null); } catch (IllegalArgumentException e) { throw new RuntimeException(e); diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java index ad40176cef..8c6f0d768c 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java @@ -24,6 +24,7 @@ import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.RpcId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rpc.Rpc; @@ -62,7 +63,7 @@ public class TbRpcService { } private void pushRpcMsgToRuleEngine(TenantId tenantId, Rpc rpc) { - TbMsg msg = TbMsg.newMsg("RPC_" + rpc.getStatus().name(), rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); + TbMsg msg = TbMsg.newMsg(TbMsgType.valueOf("RPC_" + rpc.getStatus().name()), rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); tbClusterService.pushMsgToRuleEngine(tenantId, rpc.getDeviceId(), msg, null); } diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 965b781fe9..05d71c4fca 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -51,6 +51,7 @@ import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityTrigger; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterable; @@ -102,12 +103,6 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; - /** * Created by ashvayka on 01.05.18. */ @@ -229,7 +224,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService { attributes.flatMap(KvEntry::getLongValue).ifPresent((inactivityTimeout) -> { if (inactivityTimeout > 0) { @@ -771,11 +766,11 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService filterByCustomEvent() { - return event -> event.getBody().get("msgType").textValue().equals("CUSTOM"); + protected Predicate filterByPostTelemetryEventType() { + return event -> event.getBody().get("msgType").textValue().equals(TbMsgType.POST_TELEMETRY_REQUEST.name()); } } diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java index c448c4220f..e626adf5f4 100644 --- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.event.Event; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.rule.NodeConnectionInfo; @@ -180,14 +181,14 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true); - TbMsg tbMsg = TbMsg.newMsg("CUSTOM", device.getId(), new TbMsgMetaData(), "{}", tbMsgCallback); + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, tbMsgCallback); QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null); // Pushing Message to the system actorSystem.tell(qMsg); Mockito.verify(tbMsgCallback, Mockito.timeout(10000)).onSuccess(); PageData eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000); - List events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); + List events = eventsPage.getData().stream().filter(filterByPostTelemetryEventType()).collect(Collectors.toList()); Assert.assertEquals(2, events.size()); EventInfo inEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get(); @@ -204,7 +205,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule RuleNode lastRuleNode = metaData.getNodes().stream().filter(node -> !node.getId().equals(finalRuleChain.getFirstRuleNodeId())).findFirst().get(); eventsPage = getDebugEvents(savedTenant.getId(), lastRuleNode.getId(), 1000); - events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); + events = eventsPage.getData().stream().filter(filterByPostTelemetryEventType()).collect(Collectors.toList()); Assert.assertEquals(2, events.size()); @@ -305,7 +306,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true); - TbMsg tbMsg = TbMsg.newMsg("CUSTOM", device.getId(), new TbMsgMetaData(), "{}", tbMsgCallback); + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, tbMsgCallback); QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null); // Pushing Message to the system actorSystem.tell(qMsg); @@ -313,7 +314,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule Mockito.verify(tbMsgCallback, Mockito.timeout(10000)).onSuccess(); PageData eventsPage = getDebugEvents(savedTenant.getId(), rootRuleChain.getFirstRuleNodeId(), 1000); - List events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); + List events = eventsPage.getData().stream().filter(filterByPostTelemetryEventType()).collect(Collectors.toList()); Assert.assertEquals(2, events.size()); @@ -331,7 +332,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule RuleNode lastRuleNode = secondaryMetaData.getNodes().stream().filter(node -> !node.getId().equals(finalRuleChain.getFirstRuleNodeId())).findFirst().get(); eventsPage = getDebugEvents(savedTenant.getId(), lastRuleNode.getId(), 1000); - events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); + events = eventsPage.getData().stream().filter(filterByPostTelemetryEventType()).collect(Collectors.toList()); Assert.assertEquals(2, events.size()); diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java index 9753f06aff..6216f993dc 100644 --- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.EventInfo; import org.thingsboard.server.common.data.event.EventType; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; @@ -139,7 +140,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac log.warn("attr updated"); TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true); - TbMsg tbMsg = TbMsg.newMsg("CUSTOM", device.getId(), new TbMsgMetaData(), "{}", tbMsgCallback); + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, tbMsgCallback); QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(tenantId, tbMsg, null, null); // Pushing Message to the system log.warn("before tell tbMsgCallback"); @@ -147,12 +148,12 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac log.warn("awaiting tbMsgCallback"); Mockito.verify(tbMsgCallback, Mockito.timeout(TimeUnit.SECONDS.toMillis(TIMEOUT))).onSuccess(); log.warn("awaiting events"); - List events = Awaitility.await("get debug by custom event") + List events = Awaitility.await("get debug by post telemetry event") .pollInterval(10, MILLISECONDS) .atMost(TIMEOUT, TimeUnit.SECONDS) .until(() -> { List debugEvents = getDebugEvents(tenantId, ruleChainFinal.getFirstRuleNodeId(), 1000) - .getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); + .getData().stream().filter(filterByPostTelemetryEventType()).collect(Collectors.toList()); log.warn("filtered debug events [{}]", debugEvents.size()); debugEvents.forEach((e) -> log.warn("event: {}", e)); return debugEvents; diff --git a/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java b/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java index 6c060feb14..654f934ed2 100644 --- a/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -50,7 +51,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @DaoSqlTest public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest { @@ -133,7 +133,7 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest void saveLatestTsForAssetAndDevice(List devices, Asset asset, int idx) throws ExecutionException, InterruptedException, TimeoutException { for (Device device : devices) { - TbMsg tbMsg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), + TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), getTbMsgMetadata(device.getName(), ts.get(idx)), TbMsgDataType.JSON, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java index 1872fd676f..0084b391eb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java @@ -66,16 +66,44 @@ public enum TbMsgType { RELATION_DELETED("Relation Deleted"), RELATIONS_DELETED("All Relations Deleted"), PROVISION_SUCCESS(null), - PROVISION_FAILURE(null); + PROVISION_FAILURE(null), + SEND_EMAIL(null), + + // tellSelfOnly types + GENERATOR_NODE_SELF_MSG(null, true), + + DEVICE_PROFILE_PERIODIC_SELF_MSG(null, true), + DEVICE_PROFILE_UPDATE_SELF_MSG(null, true), + DEVICE_UPDATE_SELF_MSG(null, true), + + DEDUPLICATION_TIMEOUT_SELF_MSG(null, true), + + DELAY_TIMEOUT_SELF_MSG(null, true), + + MSG_COUNT_SELF_MSG(null, true); + + public static final List NODE_CONNECTIONS = EnumSet.allOf(TbMsgType.class).stream() - .map(TbMsgType::getRuleNodeConnection).filter(Objects::nonNull).collect(Collectors.toUnmodifiableList()); + .filter(tbMsgType -> !tbMsgType.isTellSelfOnly()) + .map(TbMsgType::getRuleNodeConnection) + .filter(Objects::nonNull) + .collect(Collectors.toUnmodifiableList()); @Getter private final String ruleNodeConnection; + @Getter + private final boolean tellSelfOnly; + + TbMsgType(String ruleNodeConnection, boolean tellSelfOnly) { + this.ruleNodeConnection = ruleNodeConnection; + this.tellSelfOnly = tellSelfOnly; + } + TbMsgType(String ruleNodeConnection) { this.ruleNodeConnection = ruleNodeConnection; + this.tellSelfOnly = false; } public static String getRuleNodeConnectionOrElseOther(String msgType) { diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java index c1f9dffd17..58f2089aed 100644 --- a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java +++ b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java @@ -22,10 +22,18 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM; import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; +import static org.thingsboard.server.common.data.msg.TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG; +import static org.thingsboard.server.common.data.msg.TbMsgType.DELAY_TIMEOUT_SELF_MSG; import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED_TO_EDGE; import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UNASSIGNED_FROM_EDGE; +import static org.thingsboard.server.common.data.msg.TbMsgType.MSG_COUNT_SELF_MSG; import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_FAILURE; import static org.thingsboard.server.common.data.msg.TbMsgType.PROVISION_SUCCESS; +import static org.thingsboard.server.common.data.msg.TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG; +import static org.thingsboard.server.common.data.msg.TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG; +import static org.thingsboard.server.common.data.msg.TbMsgType.DEVICE_UPDATE_SELF_MSG; +import static org.thingsboard.server.common.data.msg.TbMsgType.GENERATOR_NODE_SELF_MSG; +import static org.thingsboard.server.common.data.msg.TbMsgType.SEND_EMAIL; class TbMsgTypeTest { @@ -35,7 +43,15 @@ class TbMsgTypeTest { ENTITY_ASSIGNED_TO_EDGE, ENTITY_UNASSIGNED_FROM_EDGE, PROVISION_FAILURE, - PROVISION_SUCCESS + PROVISION_SUCCESS, + SEND_EMAIL, + GENERATOR_NODE_SELF_MSG, + DEVICE_PROFILE_PERIODIC_SELF_MSG, + DEVICE_PROFILE_UPDATE_SELF_MSG, + DEVICE_UPDATE_SELF_MSG, + DEDUPLICATION_TIMEOUT_SELF_MSG, + DELAY_TIMEOUT_SELF_MSG, + MSG_COUNT_SELF_MSG ); diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index 9848046b11..c6c1a36694 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -29,10 +29,12 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.gen.MsgProtos; import org.thingsboard.server.common.msg.queue.TbMsgCallback; import java.io.Serializable; +import java.util.Objects; import java.util.UUID; /** @@ -42,7 +44,9 @@ import java.util.UUID; @Slf4j public final class TbMsg implements Serializable { - public static final String EMPTY = "{}"; + public static final String EMPTY_JSON_OBJECT = "{}"; + public static final String EMPTY_JSON_ARRAY = "[]"; + public static final String EMPTY_STRING = ""; private final String queueName; private final UUID id; @@ -68,61 +72,208 @@ public final class TbMsg implements Serializable { return ctx.getAndIncrementRuleNodeCounter(); } + @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { return newMsg(queueName, type, originator, null, metaData, data, ruleChainId, ruleNodeId); } + /** + * Creates a new TbMsg instance with the specified parameters. + * + *

Deprecated: This method is deprecated since version 3.5.2 and should only be used when you need to + * specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types, + * it is recommended to use the {@link #newMsg(String, TbMsgType, EntityId, CustomerId, TbMsgMetaData, String, RuleChainId, RuleNodeId)} + * method instead.

+ * + * @param queueName the name of the queue where the message will be sent + * @param type the type of the message + * @param originator the originator of the message + * @param customerId the ID of the customer associated with the message + * @param metaData the metadata of the message + * @param data the data of the message + * @param ruleChainId the ID of the rule chain associated with the message + * @param ruleNodeId the ID of the rule node associated with the message + * @return new TbMsg instance + */ + @Deprecated(since = "3.5.2") public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY); } + @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) { return newMsg(type, originator, null, metaData, data); } + @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); } + public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { + return newMsg(queueName, type, originator, null, metaData, data, ruleChainId, ruleNodeId); + } + + public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { + return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, customerId, + metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY); + } + + public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) { + return newMsg(type, originator, null, metaData, data); + } + + public static TbMsg newMsg(TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, customerId, + metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); + } + // REALLY NEW MSG + /** + * Creates a new TbMsg instance with the specified parameters. + * + *

Deprecated: This method is deprecated since version 3.5.2 and should only be used when you need to + * specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types, + * it is recommended to use the {@link #newMsg(String, TbMsgType, EntityId, TbMsgMetaData, String)} + * method instead.

+ * + * @param queueName the name of the queue where the message will be sent + * @param type the type of the message + * @param originator the originator of the message + * @param metaData the metadata of the message + * @param data the data of the message + * @return new TbMsg instance + */ + @Deprecated(since = "3.5.2") public static TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data) { return newMsg(queueName, type, originator, null, metaData, data); } + /** + * Creates a new TbMsg instance with the specified parameters. + * + *

Deprecated: This method is deprecated since version 3.5.2 and should only be used when you need to + * specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types, + * it is recommended to use the {@link #newMsg(String, TbMsgType, EntityId, CustomerId, TbMsgMetaData, String)} + * method instead.

+ * + * @param queueName the name of the queue where the message will be sent + * @param type the type of the message + * @param originator the originator of the message + * @param customerId the ID of the customer associated with the message + * @param metaData the metadata of the message + * @param data the data of the message + * @return new TbMsg instance + */ + @Deprecated(since = "3.5.2") public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); } + @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), dataType, data, null, null, null, TbMsgCallback.EMPTY); } + /** + * Creates a new TbMsg instance with the specified parameters. + * + *

Deprecated: This method is deprecated since version 3.5.2 and should only be used when you need to + * specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types, + * it is recommended to use the {@link #newMsg(TbMsgType, EntityId, TbMsgMetaData, TbMsgDataType, String)} + * method instead.

+ * + * @param type the type of the message + * @param originator the originator of the message + * @param metaData the metadata of the message + * @param dataType the dataType of the message + * @param data the data of the message + * @return new TbMsg instance + */ + @Deprecated(since = "3.5.2") public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { return newMsg(type, originator, null, metaData, dataType, data); } + public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) { + return newMsg(queueName, type, originator, null, metaData, data); + } + + public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { + return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, customerId, + metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); + } + + public static TbMsg newMsg(TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, customerId, + metaData.copy(), dataType, data, null, null, null, TbMsgCallback.EMPTY); + } + + public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { + return newMsg(type, originator, null, metaData, dataType, data); + } + // For Tests only + @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, metaData.copy(), dataType, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY); } + @Deprecated(since = "3.5.2", forRemoval = true) public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) { return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, metaData.copy(), TbMsgDataType.JSON, data, null, null, null, callback); } + /** + * Transforms an existing TbMsg instance by changing its message type, originator, metadata, and data. + * + *

Deprecated: This method is deprecated since version 3.5.2 and should only be used when you need to + * specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types, + * it is recommended to use the {@link #transformMsg(TbMsg, TbMsgType, EntityId, TbMsgMetaData, String)} + * method instead.

+ * + * + * @param tbMsg the TbMsg instance to transform + * @param type the new message type + * @param originator the new originator + * @param metaData the new metadata + * @param data the new data + * @return the transformed TbMsg instance + */ + @Deprecated(since = "3.5.2") public static TbMsg transformMsg(TbMsg tbMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, type, originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType, data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.callback); } + public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, null, + metaData.copy(), dataType, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY); + } + + public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) { + return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type.name(), originator, null, + metaData.copy(), TbMsgDataType.JSON, data, null, null, null, callback); + } + + public static TbMsg transformMsg(TbMsg tbMsg, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) { + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, type.name(), originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType, + data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.callback); + } + + public static TbMsg transformMsgOriginator(TbMsg tbMsg, EntityId originatorId) { + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, originatorId, tbMsg.getCustomerId(), tbMsg.metaData, tbMsg.dataType, + tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); + } + public static TbMsg transformMsgData(TbMsg tbMsg, String data) { return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); @@ -133,6 +284,11 @@ public final class TbMsg implements Serializable { tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } + public static TbMsg transformMsg(TbMsg tbMsg, TbMsgMetaData metadata, String data) { + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, metadata, tbMsg.dataType, + data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); + } + public static TbMsg transformMsg(TbMsg tbMsg, CustomerId customerId) { return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); @@ -185,11 +341,7 @@ public final class TbMsg implements Serializable { this.ruleChainId = ruleChainId; this.ruleNodeId = ruleNodeId; this.ctx = ctx != null ? ctx : new TbMsgProcessingCtx(); - if (callback != null) { - this.callback = callback; - } else { - this.callback = TbMsgCallback.EMPTY; - } + this.callback = Objects.requireNonNullElse(callback, TbMsgCallback.EMPTY); } public static ByteString toByteString(TbMsg msg) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 6500467af7..497540ee44 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -1151,7 +1151,7 @@ public class DefaultTransportService implements TransportService { queueName = deviceProfile.getDefaultQueueName(); } - TbMsg tbMsg = TbMsg.newMsg(queueName, tbMsgType.name(), deviceId, customerId, metaData, gson.toJson(json), ruleChainId, null); + TbMsg tbMsg = TbMsg.newMsg(queueName, tbMsgType, deviceId, customerId, metaData, gson.toJson(json), ruleChainId, null); sendToRuleEngine(tenantId, tbMsg, callback); } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 88bf80e9f9..be35e74321 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleNode; @@ -183,12 +184,41 @@ public interface TbContext { void ack(TbMsg tbMsg); + @Deprecated(since = "3.5.2", forRemoval = true) TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data); + /** + * Creates a new TbMsg instance with the specified parameters. + * + *

Deprecated: This method is deprecated since version 3.5.2 and should only be used when you need to + * specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types, + * it is recommended to use the {@link #newMsg(String, TbMsgType, EntityId, CustomerId, TbMsgMetaData, String)} + * method instead.

+ * + * @param queueName the name of the queue where the message will be sent + * @param type the type of the message + * @param originator the originator of the message + * @param customerId the ID of the customer associated with the message + * @param metaData the metadata of the message + * @param data the data of the message + * @return new TbMsg instance + */ + @Deprecated(since = "3.5.2") TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data); + @Deprecated(since = "3.5.2", forRemoval = true) TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data); + TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data); + + TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data); + + TbMsg transformMsg(TbMsg origMsg, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data); + + TbMsg transformMsg(TbMsg origMsg, TbMsgMetaData metaData, String data); + + TbMsg transformMsgOriginator(TbMsg origMsg, EntityId originator); + TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId); TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId); @@ -196,7 +226,7 @@ public interface TbContext { TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId); // TODO: Does this changes the message? - TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action); + TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, TbMsgType actionMsgType); TbMsg attributesUpdatedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List attributes); diff --git a/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java b/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java index 956cfaf40c..cb5514a82b 100644 --- a/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java +++ b/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java @@ -22,6 +22,7 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -43,7 +44,7 @@ public class TbNodeUtilsTest { ObjectNode node = JacksonUtil.newObjectNode(); node.put("data_key", "data_value"); - TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); String result = TbNodeUtils.processPattern(pattern, msg); Assert.assertEquals("ABC metadata_value data_value", result); } @@ -57,7 +58,7 @@ public class TbNodeUtilsTest { ObjectNode node = JacksonUtil.newObjectNode(); node.put("key", "data_value"); - TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); String result = TbNodeUtils.processPattern(pattern, msg); Assert.assertEquals(pattern, result); } @@ -71,7 +72,7 @@ public class TbNodeUtilsTest { ObjectNode node = JacksonUtil.newObjectNode(); node.put("key", "data_value"); - TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); String result = TbNodeUtils.processPattern(pattern, msg); Assert.assertEquals("ABC metadata_value data_value", result); } @@ -92,7 +93,7 @@ public class TbNodeUtilsTest { ObjectNode node = JacksonUtil.newObjectNode(); node.set("key1", key1Node); - TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); String result = TbNodeUtils.processPattern(pattern, msg); Assert.assertEquals("ABC metadata_value value3", result); } @@ -113,7 +114,7 @@ public class TbNodeUtilsTest { ObjectNode node = JacksonUtil.newObjectNode(); node.set("key1", key1Node); - TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); String result = TbNodeUtils.processPattern(pattern, msg); Assert.assertEquals("ABC metadata_value $[key1.key2[0].key3]", result); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index bc27154ad8..18317481bd 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.DataConstants; @@ -32,10 +33,6 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import static org.thingsboard.common.util.DonAsynchron.withCallback; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_CLEAR; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UPDATED; @Slf4j @@ -62,11 +59,11 @@ public abstract class TbAbstractAlarmNode processAlarm(TbContext ctx, TbMsg msg); - protected ListenableFuture buildAlarmDetails(TbContext ctx, TbMsg msg, JsonNode previousDetails) { + protected ListenableFuture buildAlarmDetails(TbMsg msg, JsonNode previousDetails) { try { TbMsg dummyMsg = msg; if (previousDetails != null) { TbMsgMetaData metaData = msg.getMetaData().copy(); metaData.putValue(PREV_ALARM_DETAILS, JacksonUtil.toString(previousDetails)); - dummyMsg = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), metaData, msg.getData()); + dummyMsg = TbMsg.transformMsg(msg, metaData); } return scriptEngine.executeJsonAsync(dummyMsg); } catch (Exception e) { @@ -101,7 +98,7 @@ public abstract class TbAbstractAlarmNode ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), alarmAction), throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable)); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java index a8d7d4985f..d0215a441f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java @@ -71,7 +71,7 @@ public class TbClearAlarmNode extends TbAbstractAlarmNode clearAlarm(TbContext ctx, TbMsg msg, Alarm alarm) { ctx.logJsEvalRequest(); - ListenableFuture asyncDetails = buildAlarmDetails(ctx, msg, alarm.getDetails()); + ListenableFuture asyncDetails = buildAlarmDetails(msg, alarm.getDetails()); return Futures.transform(asyncDetails, details -> { ctx.logJsEvalResponse(); AlarmApiCallResult result = ctx.getAlarmService().clearAlarm(ctx.getTenantId(), alarm.getId(), System.currentTimeMillis(), details); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java index 84e82b76e8..b08fd1a562 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java @@ -120,7 +120,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode future = createRelationIfAbsent(ctx, msg, entity, relationType); return Futures.transform(future, result -> { if (result && config.isChangeOriginatorToRelatedEntity()) { - TbMsg tbMsg = ctx.transformMsg(msg, msg.getType(), entity.getEntityId(), msg.getMetaData(), msg.getData()); + TbMsg tbMsg = ctx.transformMsgOriginator(msg, entity.getEntityId()); return new RelationContainer(tbMsg, result); } return new RelationContainer(msg, result); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java index d9a99c3229..0fd99651a1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java @@ -24,6 +24,8 @@ import org.thingsboard.rule.engine.api.TbNode; 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.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -32,9 +34,6 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; -import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; - @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -48,8 +47,6 @@ import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCES ) public class TbMsgCountNode implements TbNode { - private static final String TB_MSG_COUNT_NODE_MSG = "TbMsgCountNodeMsg"; - private AtomicLong messagesProcessed = new AtomicLong(0); private final Gson gson = new Gson(); private UUID nextTickId; @@ -68,7 +65,7 @@ public class TbMsgCountNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.getType().equals(TB_MSG_COUNT_NODE_MSG) && msg.getId().equals(nextTickId)) { + if (msg.getType().equals(TbMsgType.MSG_COUNT_SELF_MSG.name()) && msg.getId().equals(nextTickId)) { JsonObject telemetryJson = new JsonObject(); telemetryJson.addProperty(this.telemetryPrefix + "_" + ctx.getServiceId(), messagesProcessed.longValue()); @@ -77,8 +74,8 @@ public class TbMsgCountNode implements TbNode { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("delta", Long.toString(System.currentTimeMillis() - lastScheduledTs + delay)); - TbMsg tbMsg = TbMsg.newMsg(msg.getQueueName(), POST_TELEMETRY_REQUEST.name(), ctx.getTenantId(), msg.getCustomerId(), metaData, gson.toJson(telemetryJson)); - ctx.enqueueForTellNext(tbMsg, SUCCESS); + TbMsg tbMsg = TbMsg.newMsg(msg.getQueueName(), TbMsgType.POST_TELEMETRY_REQUEST, ctx.getTenantId(), msg.getCustomerId(), metaData, gson.toJson(telemetryJson)); + ctx.enqueueForTellNext(tbMsg, TbNodeConnectionType.SUCCESS); scheduleTickMsg(ctx, tbMsg); } else { messagesProcessed.incrementAndGet(); @@ -93,7 +90,7 @@ public class TbMsgCountNode implements TbNode { } lastScheduledTs = lastScheduledTs + delay; long curDelay = Math.max(0L, (lastScheduledTs - curTs)); - TbMsg tickMsg = ctx.newMsg(null, TB_MSG_COUNT_NODE_MSG, ctx.getSelfId(), msg != null ? msg.getCustomerId() : null, new TbMsgMetaData(), ""); + TbMsg tickMsg = ctx.newMsg(null, TbMsgType.MSG_COUNT_SELF_MSG, ctx.getSelfId(), msg != null ? msg.getCustomerId() : null, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); nextTickId = tickMsg.getId(); ctx.tellSelf(tickMsg, curDelay); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java index 4c8a5b28e1..1c487b2352 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java @@ -101,13 +101,13 @@ public class TbSnsNode extends TbAbstractExternalNode { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(MESSAGE_ID, result.getMessageId()); metaData.putValue(REQUEST_ID, result.getSdkResponseMetadata().getRequestId()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java index f8ebc8e295..d5f3f842e9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java @@ -27,7 +27,6 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; @@ -88,15 +87,15 @@ public class TbSqsNode extends TbAbstractExternalNode { public void onMsg(TbContext ctx, TbMsg msg) { withCallback(publishMessageAsync(ctx, msg), m -> tellSuccess(ctx, m), - t -> tellFailure(ctx, processException(ctx, msg, t), t)); + t -> tellFailure(ctx, processException(msg, t), t)); ackIfNeeded(ctx, msg); } private ListenableFuture publishMessageAsync(TbContext ctx, TbMsg msg) { - return ctx.getExternalCallExecutor().executeAsync(() -> publishMessage(ctx, msg)); + return ctx.getExternalCallExecutor().executeAsync(() -> publishMessage(msg)); } - private TbMsg publishMessage(TbContext ctx, TbMsg msg) { + private TbMsg publishMessage(TbMsg msg) { String queueUrl = TbNodeUtils.processPattern(this.config.getQueueUrlPattern(), msg); SendMessageRequest sendMsgRequest = new SendMessageRequest(); sendMsgRequest.withQueueUrl(queueUrl); @@ -115,10 +114,10 @@ public class TbSqsNode extends TbAbstractExternalNode { sendMsgRequest.withMessageGroupId(msg.getOriginator().toString()); } SendMessageResult result = this.sqsClient.sendMessage(sendMsgRequest); - return processSendMessageResult(ctx, msg, result); + return processSendMessageResult(msg, result); } - private TbMsg processSendMessageResult(TbContext ctx, TbMsg origMsg, SendMessageResult result) { + private TbMsg processSendMessageResult(TbMsg origMsg, SendMessageResult result) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(MESSAGE_ID, result.getMessageId()); metaData.putValue(REQUEST_ID, result.getSdkResponseMetadata().getRequestId()); @@ -131,13 +130,13 @@ public class TbSqsNode extends TbAbstractExternalNode { if (!StringUtils.isEmpty(result.getSequenceNumber())) { metaData.putValue(SEQUENCE_NUMBER, result.getSequenceNumber()); } - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } - private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { + private TbMsg processException(TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 75b63c59e9..7c3d6424e5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -30,6 +30,8 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; @@ -41,7 +43,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static org.thingsboard.common.util.DonAsynchron.withCallback; -import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; @Slf4j @RuleNode( @@ -58,8 +59,6 @@ import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCES public class TbMsgGeneratorNode implements TbNode { - private static final String TB_MSG_GENERATOR_NODE_MSG = "TbMsgGeneratorNodeMsg"; - private TbMsgGeneratorNodeConfiguration config; private ScriptEngine scriptEngine; private long delay; @@ -107,13 +106,13 @@ public class TbMsgGeneratorNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { log.trace("onMsg, config {}, msg {}", config, msg); - if (initialized.get() && msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) { + if (initialized.get() && msg.getType().equals(TbMsgType.GENERATOR_NODE_SELF_MSG.name()) && msg.getId().equals(nextTickId)) { TbStopWatch sw = TbStopWatch.create(); withCallback(generate(ctx, msg), m -> { log.trace("onMsg onSuccess callback, took {}ms, config {}, msg {}", sw.stopAndGetTotalTimeMillis(), config, msg); if (initialized.get() && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { - ctx.enqueueForTellNext(m, SUCCESS); + ctx.enqueueForTellNext(m, TbNodeConnectionType.SUCCESS); scheduleTickMsg(ctx); currentMsgCount++; } @@ -137,7 +136,7 @@ public class TbMsgGeneratorNode implements TbNode { } lastScheduledTs = lastScheduledTs + delay; long curDelay = Math.max(0L, (lastScheduledTs - curTs)); - TbMsg tickMsg = ctx.newMsg(config.getQueueName(), TB_MSG_GENERATOR_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), ""); + TbMsg tickMsg = ctx.newMsg(config.getQueueName(), TbMsgType.GENERATOR_NODE_SELF_MSG, ctx.getSelfId(), new TbMsgMetaData(), TbMsg.EMPTY_STRING); nextTickId = tickMsg.getId(); ctx.tellSelf(tickMsg, curDelay); } @@ -145,7 +144,7 @@ public class TbMsgGeneratorNode implements TbNode { private ListenableFuture generate(TbContext ctx, TbMsg msg) { log.trace("generate, config {}", config); if (prevMsg == null) { - prevMsg = ctx.newMsg(config.getQueueName(), "", originatorId, msg.getCustomerId(), new TbMsgMetaData(), "{}"); + prevMsg = ctx.newMsg(config.getQueueName(), "", originatorId, msg.getCustomerId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); } if (initialized.get()) { ctx.logJsEvalRequest(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index 5654b4095a..fae40ff5f5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; @@ -60,10 +61,7 @@ import java.util.concurrent.TimeUnit; @Slf4j public class TbMsgDeduplicationNode implements TbNode { - private static final String TB_MSG_DEDUPLICATION_TIMEOUT_MSG = "TbMsgDeduplicationNodeMsg"; public static final int TB_MSG_DEDUPLICATION_RETRY_DELAY = 10; - private static final String EMPTY_DATA = ""; - private static final TbMsgMetaData EMPTY_META_DATA = new TbMsgMetaData(); private TbMsgDeduplicationNodeConfiguration config; @@ -82,7 +80,7 @@ public class TbMsgDeduplicationNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - if (TB_MSG_DEDUPLICATION_TIMEOUT_MSG.equals(msg.getType())) { + if (TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG.name().equals(msg.getType())) { processDeduplication(ctx, msg.getOriginator()); } else { processOnRegularMsg(ctx, msg); @@ -210,7 +208,7 @@ public class TbMsgDeduplicationNode implements TbNode { } private void scheduleTickMsg(TbContext ctx, EntityId deduplicationId) { - ctx.tellSelf(ctx.newMsg(null, TB_MSG_DEDUPLICATION_TIMEOUT_MSG, deduplicationId, EMPTY_META_DATA, EMPTY_DATA), deduplicationInterval + 1); + ctx.tellSelf(ctx.newMsg(null, TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG, deduplicationId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING), deduplicationInterval + 1); } private String getMergedData(List msgs) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index 00ba3acc50..dabba5970a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -23,6 +23,8 @@ import org.thingsboard.rule.engine.api.TbNode; 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.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -32,8 +34,6 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; -import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; - @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -50,8 +50,6 @@ import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCES ) public class TbMsgDelayNode implements TbNode { - private static final String TB_MSG_DELAY_NODE_MSG = "TbMsgDelayNodeMsg"; - private TbMsgDelayNodeConfiguration config; private Map pendingMsgs; @@ -63,7 +61,7 @@ public class TbMsgDelayNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.getType().equals(TB_MSG_DELAY_NODE_MSG)) { + if (msg.getType().equals(TbMsgType.DELAY_TIMEOUT_SELF_MSG.name())) { TbMsg pendingMsg = pendingMsgs.remove(UUID.fromString(msg.getData())); if (pendingMsg != null) { ctx.enqueueForTellNext( @@ -75,13 +73,13 @@ public class TbMsgDelayNode implements TbNode { pendingMsg.getMetaData(), pendingMsg.getData() ), - SUCCESS + TbNodeConnectionType.SUCCESS ); } } else { if (pendingMsgs.size() < config.getMaxPendingMsgs()) { pendingMsgs.put(msg.getId(), msg); - TbMsg tickMsg = ctx.newMsg(null, TB_MSG_DELAY_NODE_MSG, ctx.getSelfId(), msg.getCustomerId(), new TbMsgMetaData(), msg.getId().toString()); + TbMsg tickMsg = ctx.newMsg(null, TbMsgType.DELAY_TIMEOUT_SELF_MSG, ctx.getSelfId(), msg.getCustomerId(), TbMsgMetaData.EMPTY, msg.getId().toString()); ctx.tellSelf(tickMsg, getDelay(msg)); ctx.ack(msg); } else { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java index 3b927f05a6..b3f1c87171 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java @@ -28,7 +28,6 @@ import com.google.pubsub.v1.PubsubMessage; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; @@ -103,28 +102,28 @@ public class TbPubSubNode extends TbAbstractExternalNode { ApiFuture messageIdFuture = this.pubSubClient.publish(pubsubMessageBuilder.build()); ApiFutures.addCallback(messageIdFuture, new ApiFutureCallback() { public void onSuccess(String messageId) { - TbMsg next = processPublishResult(ctx, msg, messageId); + TbMsg next = processPublishResult(msg, messageId); tellSuccess(ctx, next); } public void onFailure(Throwable t) { - TbMsg next = processException(ctx, msg, t); + TbMsg next = processException(msg, t); tellFailure(ctx, next, t); } }, ctx.getExternalCallExecutor()); } - private TbMsg processPublishResult(TbContext ctx, TbMsg origMsg, String messageId) { + private TbMsg processPublishResult(TbMsg origMsg, String messageId) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(MESSAGE_ID, messageId); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } - private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { + private TbMsg processException(TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } private Publisher initPubSubClient() throws IOException { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index 80baa86505..a336fc1f77 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -165,24 +165,24 @@ public class TbKafkaNode extends TbAbstractExternalNode { private void processRecord(TbContext ctx, TbMsg msg, RecordMetadata metadata, Exception e) { if (e == null) { - tellSuccess(ctx, processResponse(ctx, msg, metadata)); + tellSuccess(ctx, processResponse(msg, metadata)); } else { - tellFailure(ctx, processException(ctx, msg, e), e); + tellFailure(ctx, processException(msg, e), e); } } - private TbMsg processResponse(TbContext ctx, TbMsg origMsg, RecordMetadata recordMetadata) { + private TbMsg processResponse(TbMsg origMsg, RecordMetadata recordMetadata) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(OFFSET, String.valueOf(recordMetadata.offset())); metaData.putValue(PARTITION, String.valueOf(recordMetadata.partition())); metaData.putValue(TOPIC, recordMetadata.topic()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } - private TbMsg processException(TbContext ctx, TbMsg origMsg, Exception e) { + private TbMsg processException(TbMsg origMsg, Exception e) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index ae661e48e4..a52641bedc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbEmail; @@ -27,6 +26,9 @@ import org.thingsboard.rule.engine.api.TbNode; 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.server.common.data.StringUtils; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -34,9 +36,6 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; -import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; - @Slf4j @RuleNode( type = ComponentType.TRANSFORMATION, @@ -68,7 +67,7 @@ public class TbMsgToEmailNode implements TbNode { try { TbEmail email = convert(msg); TbMsg emailMsg = buildEmailMsg(ctx, msg, email); - ctx.tellNext(emailMsg, SUCCESS); + ctx.tellNext(emailMsg, TbNodeConnectionType.SUCCESS); } catch (Exception ex) { log.warn("Can not convert message to email " + ex.getMessage()); ctx.tellFailure(msg, ex); @@ -77,7 +76,7 @@ public class TbMsgToEmailNode implements TbNode { private TbMsg buildEmailMsg(TbContext ctx, TbMsg msg, TbEmail email) throws JsonProcessingException { String emailJson = JacksonUtil.toString(email); - return ctx.transformMsg(msg, SEND_EMAIL_TYPE, msg.getOriginator(), msg.getMetaData().copy(), emailJson); + return ctx.transformMsg(msg, TbMsgType.SEND_EMAIL, msg.getOriginator(), msg.getMetaData().copy(), emailJson); } private TbEmail convert(TbMsg msg) throws IOException { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java index eec403cdb9..66b23a29af 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java @@ -16,9 +16,8 @@ package org.thingsboard.rule.engine.mail; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.StringUtils; import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbEmail; @@ -26,6 +25,8 @@ 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.external.TbAbstractExternalNode; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -50,7 +51,6 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; public class TbSendEmailNode extends TbAbstractExternalNode { private static final String MAIL_PROP = "mail."; - static final String SEND_EMAIL_TYPE = "SEND_EMAIL"; private TbSendEmailNodeConfiguration config; private JavaMailSenderImpl mailSender; @@ -101,7 +101,7 @@ public class TbSendEmailNode extends TbAbstractExternalNode { } private void validateType(String type) { - if (!SEND_EMAIL_TYPE.equals(type)) { + if (!TbMsgType.SEND_EMAIL.name().equals(type)) { log.warn("Not expected msg type [{}] for SendEmail Node", type); throw new IllegalStateException("Not expected msg type " + type + " for SendEmail Node"); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index d4bdd320d1..25b9205602 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -115,7 +115,7 @@ public class CalculateDeltaNode implements TbNode { long period = previousData != null ? currentTs - previousData.ts : 0; result.put(config.getPeriodValueKey(), period); } - ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(result))); + ctx.tellSuccess(TbMsg.transformMsgData(msg, JacksonUtil.toString(result))); }, t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java index 569c76c2a2..4cf564fd7b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java @@ -43,10 +43,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL; -import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_FIRST; -import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.MAX_FETCH_SIZE; - /** * Created by mshvayka on 04.09.18. */ @@ -76,7 +72,7 @@ public class TbGetTelemetryNode implements TbNode { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbGetTelemetryNodeConfiguration.class); tsKeyNames = config.getLatestTsKeyNames(); - limit = config.getFetchMode().equals(FETCH_MODE_ALL) ? validateLimit(config.getLimit()) : 1; + limit = config.getFetchMode().equals(TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL) ? validateLimit(config.getLimit()) : 1; fetchMode = config.getFetchMode(); orderByFetchAll = config.getOrderBy(); if (StringUtils.isEmpty(orderByFetchAll)) { @@ -86,7 +82,7 @@ public class TbGetTelemetryNode implements TbNode { } Aggregation parseAggregationConfig(String aggName) { - if (StringUtils.isEmpty(aggName) || !fetchMode.equals(FETCH_MODE_ALL)) { + if (StringUtils.isEmpty(aggName) || !fetchMode.equals(TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL)) { return Aggregation.NONE; } return Aggregation.valueOf(aggName); @@ -103,7 +99,7 @@ public class TbGetTelemetryNode implements TbNode { ListenableFuture> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(interval, keys)); DonAsynchron.withCallback(list, data -> { process(data, msg, keys); - ctx.tellSuccess(ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData())); + ctx.tellSuccess(msg); }, error -> ctx.tellFailure(msg, error), ctx.getDbCallbackExecutor()); } catch (Exception e) { ctx.tellFailure(msg, e); @@ -124,9 +120,9 @@ public class TbGetTelemetryNode implements TbNode { private String getOrderBy() { switch (fetchMode) { - case FETCH_MODE_ALL: + case TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL: return orderByFetchAll; - case FETCH_MODE_FIRST: + case TbGetTelemetryNodeConfiguration.FETCH_MODE_FIRST: return ASC_ORDER; default: return DESC_ORDER; @@ -135,7 +131,7 @@ public class TbGetTelemetryNode implements TbNode { private void process(List entries, TbMsg msg, List keys) { ObjectNode resultNode = JacksonUtil.newObjectNode(JacksonUtil.ALLOW_UNQUOTED_FIELD_NAMES_MAPPER); - if (FETCH_MODE_ALL.equals(fetchMode)) { + if (TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL.equals(fetchMode)) { entries.forEach(entry -> processArray(resultNode, entry)); } else { entries.forEach(entry -> processSingle(resultNode, entry)); @@ -216,7 +212,7 @@ public class TbGetTelemetryNode implements TbNode { if (limit != 0) { return limit; } else { - return MAX_FETCH_SIZE; + return TbGetTelemetryNodeConfiguration.MAX_FETCH_SIZE; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java index 8fac7b1683..ccb7082e01 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java @@ -25,7 +25,6 @@ import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.mqtt.MqttConnectResult; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; @@ -41,6 +40,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import javax.net.ssl.SSLException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -58,7 +58,7 @@ import java.util.concurrent.TimeoutException; ) public class TbMqttNode extends TbAbstractExternalNode { - private static final Charset UTF8 = Charset.forName("UTF-8"); + private static final Charset UTF8 = StandardCharsets.UTF_8; private static final String ERROR = "error"; @@ -85,17 +85,17 @@ public class TbMqttNode extends TbAbstractExternalNode { if (future.isSuccess()) { tellSuccess(ctx, msg); } else { - tellFailure(ctx, processException(ctx, msg, future.cause()), future.cause()); + tellFailure(ctx, processException(msg, future.cause()), future.cause()); } } ); ackIfNeeded(ctx, msg); } - private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) { + private TbMsg processException(TbMsg origMsg, Throwable e) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 70f15935fc..9e2719bf53 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -189,7 +190,7 @@ class AlarmState { metaData.putValue(DataConstants.IS_CLEARED_ALARM, Boolean.TRUE.toString()); } setAlarmConditionMetadata(ruleState, metaData); - TbMsg newMsg = ctx.newMsg(lastMsgQueueName != null ? lastMsgQueueName : null, "ALARM", + TbMsg newMsg = ctx.newMsg(lastMsgQueueName != null ? lastMsgQueueName : null, TbMsgType.ALARM, originator, msg != null ? msg.getCustomerId() : null, metaData, data); ctx.enqueueForTellNext(newMsg, relationType); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index 4397200fe4..e368358299 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -224,7 +224,7 @@ class DeviceState { private boolean processAttributesDeleteNotification(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { boolean stateChanged = false; List keys = new ArrayList<>(); - new JsonParser().parse(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray().forEach(e -> keys.add(e.getAsString())); + JsonParser.parseString(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray().forEach(e -> keys.add(e.getAsString())); String scope = msg.getMetaData().getValue(DataConstants.SCOPE); if (StringUtils.isEmpty(scope)) { scope = DataConstants.CLIENT_SCOPE; @@ -252,7 +252,7 @@ class DeviceState { private boolean processAttributes(TbContext ctx, TbMsg msg, String scope) throws ExecutionException, InterruptedException { boolean stateChanged = false; - Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())); + Set attributes = JsonConverter.convertToAttributes(JsonParser.parseString(msg.getData())); if (!attributes.isEmpty()) { SnapshotUpdate update = merge(latestValues, attributes, scope); for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { @@ -267,7 +267,7 @@ class DeviceState { protected boolean processTelemetry(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { boolean stateChanged = false; - Map> tsKvMap = JsonConverter.convertToSortedTelemetry(new JsonParser().parse(msg.getData()), msg.getMetaDataTs()); + Map> tsKvMap = JsonConverter.convertToSortedTelemetry(JsonParser.parseString(msg.getData()), msg.getMetaDataTs()); // iterate over data by ts (ASC order). for (Map.Entry> entry : tsKvMap.entrySet()) { Long ts = entry.getKey(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index a8b3ef4f5f..0abb15f279 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -45,9 +46,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_DELETED; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UPDATED; - @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -62,9 +60,6 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UPDATED; configDirective = "tbDeviceProfileConfig" ) public class TbDeviceProfileNode implements TbNode { - private static final String PERIODIC_MSG_TYPE = "TbDeviceProfilePeriodicMsg"; - private static final String PROFILE_UPDATE_MSG_TYPE = "TbDeviceProfileUpdateMsg"; - private static final String DEVICE_UPDATE_MSG_TYPE = "TbDeviceUpdateMsg"; private TbDeviceProfileNodeConfiguration config; private RuleEngineDeviceProfileCache cache; @@ -109,12 +104,16 @@ public class TbDeviceProfileNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { EntityType originatorType = msg.getOriginator().getEntityType(); - if (msg.getType().equals(PERIODIC_MSG_TYPE)) { + if (msg.getType().equals(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG.name())) { scheduleAlarmHarvesting(ctx, msg); harvestAlarms(ctx, System.currentTimeMillis()); - } else if (msg.getType().equals(PROFILE_UPDATE_MSG_TYPE)) { + return; + } + if (msg.getType().equals(TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG.name())) { updateProfile(ctx, new DeviceProfileId(UUID.fromString(msg.getData()))); - } else if (msg.getType().equals(DEVICE_UPDATE_MSG_TYPE)) { + return; + } + if (msg.getType().equals(TbMsgType.DEVICE_UPDATE_SELF_MSG.name())) { JsonNode data = JacksonUtil.toJsonNode(msg.getData()); DeviceId deviceId = new DeviceId(UUID.fromString(data.get("deviceId").asText())); if (data.has("profileId")) { @@ -122,28 +121,28 @@ public class TbDeviceProfileNode implements TbNode { } else { removeDeviceState(deviceId); } - } else { - if (EntityType.DEVICE.equals(originatorType)) { - DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); - if (msg.getType().equals(ENTITY_UPDATED.name())) { - invalidateDeviceProfileCache(deviceId, msg.getData()); - ctx.tellSuccess(msg); - } else if (msg.getType().equals(ENTITY_DELETED.name())) { - removeDeviceState(deviceId); - ctx.tellSuccess(msg); + return; + } + if (EntityType.DEVICE.equals(originatorType)) { + DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); + if (msg.getType().equals(TbMsgType.ENTITY_UPDATED.name())) { + invalidateDeviceProfileCache(deviceId, msg.getData()); + ctx.tellSuccess(msg); + } else if (msg.getType().equals(TbMsgType.ENTITY_DELETED.name())) { + removeDeviceState(deviceId); + ctx.tellSuccess(msg); + } else { + DeviceState deviceState = getOrCreateDeviceState(ctx, deviceId, null); + if (deviceState != null) { + deviceState.process(ctx, msg); } else { - DeviceState deviceState = getOrCreateDeviceState(ctx, deviceId, null); - if (deviceState != null) { - deviceState.process(ctx, msg); - } else { - log.info("Device was not found! Most probably device [" + deviceId + "] has been removed from the database. Acknowledging msg."); - ctx.ack(msg); - } + log.info("Device was not found! Most probably device [" + deviceId + "] has been removed from the database. Acknowledging msg."); + ctx.ack(msg); } - } else { - ctx.tellSuccess(msg); } + return; } + ctx.tellSuccess(msg); } @Override @@ -171,7 +170,7 @@ public class TbDeviceProfileNode implements TbNode { } protected void scheduleAlarmHarvesting(TbContext ctx, TbMsg msg) { - TbMsg periodicCheck = TbMsg.newMsg(PERIODIC_MSG_TYPE, ctx.getTenantId(), msg != null ? msg.getCustomerId() : null, TbMsgMetaData.EMPTY, "{}"); + TbMsg periodicCheck = TbMsg.newMsg(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG, ctx.getTenantId(), msg != null ? msg.getCustomerId() : null, TbMsgMetaData.EMPTY, "{}"); ctx.tellSelf(periodicCheck, TimeUnit.MINUTES.toMillis(1)); } @@ -196,7 +195,7 @@ public class TbDeviceProfileNode implements TbNode { } protected void onProfileUpdate(DeviceProfile profile) { - ctx.tellSelf(TbMsg.newMsg(PROFILE_UPDATE_MSG_TYPE, ctx.getTenantId(), TbMsgMetaData.EMPTY, profile.getId().getId().toString()), 0L); + ctx.tellSelf(TbMsg.newMsg(TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG, ctx.getTenantId(), TbMsgMetaData.EMPTY, profile.getId().getId().toString()), 0L); } private void onDeviceUpdate(DeviceId deviceId, DeviceProfile deviceProfile) { @@ -205,7 +204,7 @@ public class TbDeviceProfileNode implements TbNode { if (deviceProfile != null) { msgData.put("deviceProfileId", deviceProfile.getId().getId().toString()); } - ctx.tellSelf(TbMsg.newMsg(DEVICE_UPDATE_MSG_TYPE, ctx.getTenantId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(msgData)), 0L); + ctx.tellSelf(TbMsg.newMsg(TbMsgType.DEVICE_UPDATE_SELF_MSG, ctx.getTenantId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(msgData)), 0L); } protected void invalidateDeviceProfileCache(DeviceId deviceId, String deviceJson) { @@ -218,7 +217,7 @@ public class TbDeviceProfileNode implements TbNode { removeDeviceState(deviceId); } } catch (IllegalArgumentException e) { - log.debug("[{}] Received device update notification with non-device msg body: [{}][{}]", ctx.getSelfId(), deviceId, e); + log.debug("[{}] Received device update notification with non-device msg body: [{}]", ctx.getSelfId(), deviceId, e); } } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java index 4ae634902f..b9cc8209a7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java @@ -24,7 +24,6 @@ import com.rabbitmq.client.MessageProperties; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; @@ -86,7 +85,7 @@ public class TbRabbitMqNode extends TbAbstractExternalNode { public void onMsg(TbContext ctx, TbMsg msg) { withCallback(publishMessageAsync(ctx, msg), m -> tellSuccess(ctx, m), - t -> tellFailure(ctx, processException(ctx, msg, t), t)); + t -> tellFailure(ctx, processException(msg, t), t)); ackIfNeeded(ctx, msg); } @@ -115,10 +114,10 @@ public class TbRabbitMqNode extends TbAbstractExternalNode { return msg; } - private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { + private TbMsg processException(TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 81033191d6..70a22a692e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -203,7 +203,7 @@ public class TbHttpClient { future.addCallback(new ListenableFutureCallback<>() { @Override public void onFailure(Throwable throwable) { - onFailure.accept(processException(ctx, msg, throwable), throwable); + onFailure.accept(processException(msg, throwable), throwable); } @Override @@ -211,7 +211,7 @@ public class TbHttpClient { if (responseEntity.getStatusCode().is2xxSuccessful()) { onSuccess.accept(processResponse(ctx, msg, responseEntity)); } else { - onFailure.accept(processFailureResponse(ctx, msg, responseEntity), null); + onFailure.accept(processFailureResponse(msg, responseEntity), null); } } }); @@ -260,8 +260,8 @@ public class TbHttpClient { metaData.putValue(STATUS_CODE, response.getStatusCode().value() + ""); metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); headersToMetaData(response.getHeaders(), metaData::putValue); - String body = response.getBody() == null ? "{}" : response.getBody(); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, body); + String body = response.getBody() == null ? TbMsg.EMPTY_JSON_OBJECT : response.getBody(); + return ctx.transformMsg(origMsg, metaData, body); } void headersToMetaData(Map> headers, BiConsumer consumer) { @@ -279,17 +279,17 @@ public class TbHttpClient { }); } - private TbMsg processFailureResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { + private TbMsg processFailureResponse(TbMsg origMsg, ResponseEntity response) { TbMsgMetaData metaData = origMsg.getMetaData(); metaData.putValue(STATUS, response.getStatusCode().name()); metaData.putValue(STATUS_CODE, response.getStatusCode().value() + ""); metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); metaData.putValue(ERROR_BODY, response.getBody()); headersToMetaData(response.getHeaders(), metaData::putValue); - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } - private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) { + private TbMsg processException(TbMsg origMsg, Throwable e) { TbMsgMetaData metaData = origMsg.getMetaData(); metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); if (e instanceof RestClientResponseException) { @@ -298,7 +298,7 @@ public class TbHttpClient { metaData.putValue(STATUS_CODE, restClientResponseException.getRawStatusCode() + ""); metaData.putValue(ERROR_BODY, restClientResponseException.getResponseBodyAsString()); } - return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + return TbMsg.transformMsg(origMsg, metaData); } private HttpHeaders prepareHeaders(TbMsg msg) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java index 94b0e5d078..2a4df82715 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java @@ -18,7 +18,6 @@ package org.thingsboard.rule.engine.rest; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java index 5f9a2f63ed..26d22b5ef2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java @@ -57,7 +57,6 @@ public class TbSendRPCRequestNode implements TbNode { private Random random = new Random(); private Gson gson = new Gson(); - private JsonParser jsonParser = new JsonParser(); private TbSendRpcRequestNodeConfiguration config; @Override @@ -67,7 +66,7 @@ public class TbSendRPCRequestNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - JsonObject json = jsonParser.parse(msg.getData()).getAsJsonObject(); + JsonObject json = JsonParser.parseString(msg.getData()).getAsJsonObject(); String tmp; if (msg.getOriginator().getEntityType() != EntityType.DEVICE) { ctx.tellFailure(msg, new RuntimeException("Message originator is not a device entity!")); @@ -117,7 +116,7 @@ public class TbSendRPCRequestNode implements TbNode { ctx.getRpcService().sendRpcRequestToDevice(request, ruleEngineDeviceRpcResponse -> { if (ruleEngineDeviceRpcResponse.getError().isEmpty()) { - TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), ruleEngineDeviceRpcResponse.getResponse().orElse("{}")); + TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), ruleEngineDeviceRpcResponse.getResponse().orElse(TbMsg.EMPTY_JSON_OBJECT)); ctx.enqueueForTellNext(next, TbNodeConnectionType.SUCCESS); } else { TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), wrap("error", ruleEngineDeviceRpcResponse.getError().get().name())); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index 5cc5dfe23f..4d1a092097 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.NoSuchElementException; @@ -74,14 +73,7 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode metaDataMap.remove(key)); + keysToDelete.forEach(metaDataMap::remove); metaData = new TbMsgMetaData(metaDataMap); } else { JsonNode dataNode = JacksonUtil.toJsonNode(msgData); @@ -94,7 +94,7 @@ public class TbDeleteKeysNode implements TbNode { if (keysToDelete.isEmpty()) { ctx.tellSuccess(msg); } else { - ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), metaData, msgData)); + ctx.tellSuccess(TbMsg.transformMsg(msg, metaData, msgData)); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java index da7d765a95..0e85a50c34 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java @@ -70,7 +70,7 @@ public class TbJsonPathNode implements TbNode { if (!TbJsonPathNodeConfiguration.DEFAULT_JSON_PATH.equals(this.jsonPathValue)) { try { Object jsonPathData = jsonPath.read(msg.getData(), this.configurationJsonPath); - ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(jsonPathData))); + ctx.tellSuccess(TbMsg.transformMsgData(msg, JacksonUtil.toString(jsonPathData))); } catch (PathNotFoundException e) { ctx.tellFailure(msg, e); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java index 3fce7494ea..88e6dc1a8b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java @@ -74,7 +74,7 @@ public class TbRenameKeysNode implements TbNode { } metaData = new TbMsgMetaData(metaDataMap); } else { - JsonNode dataNode = JacksonUtil.toJsonNode(msg.getData()); + JsonNode dataNode = JacksonUtil.toJsonNode(data); if (dataNode.isObject()) { ObjectNode msgData = (ObjectNode) dataNode; for (Map.Entry entry : renameKeysMapping.entrySet()) { @@ -89,7 +89,7 @@ public class TbRenameKeysNode implements TbNode { } } if (msgChanged) { - ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), metaData, data)); + ctx.tellSuccess(TbMsg.transformMsg(msg, metaData, data)); } else { ctx.tellSuccess(msg); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java index 1ee7ab7b03..8959d12c9f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java @@ -64,7 +64,7 @@ public class TbSplitArrayMsgNode implements TbNode { if (data.isEmpty()) { ctx.ack(msg); } else if (data.size() == 1) { - ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(data.get(0)))); + ctx.tellSuccess(TbMsg.transformMsgData(msg, JacksonUtil.toString(data.get(0)))); } else { TbMsgCallbackWrapper wrapper = new MultipleTbMsgsCallbackWrapper(data.size(), new TbMsgCallback() { @Override @@ -78,7 +78,7 @@ public class TbSplitArrayMsgNode implements TbNode { } }); data.forEach(msgNode -> { - TbMsg outMsg = TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(msgNode)); + TbMsg outMsg = TbMsg.transformMsgData(msg, JacksonUtil.toString(msgNode)); ctx.enqueueForTellNext(outMsg, TbNodeConnectionType.SUCCESS, wrapper::onSuccess, wrapper::onFailure); }); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 9173b7143c..33c6071769 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -34,10 +34,12 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest; import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; @@ -45,6 +47,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -67,11 +70,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.DataConstants.IS_CLEARED_ALARM; -import static org.thingsboard.server.common.data.DataConstants.IS_EXISTING_ALARM; -import static org.thingsboard.server.common.data.DataConstants.IS_NEW_ALARM; -import static org.thingsboard.server.common.data.alarm.AlarmSeverity.CRITICAL; -import static org.thingsboard.server.common.data.alarm.AlarmSeverity.WARNING; @RunWith(MockitoJUnitRunner.class) public class TbAlarmNodeTest { @@ -108,10 +106,10 @@ public class TbAlarmNodeTest { } @Test - public void newAlarmCanBeCreated() throws ScriptException, IOException { + public void newAlarmCanBeCreated() { initWithCreateAlarmScript(); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long ts = msg.getTs(); when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); @@ -121,7 +119,7 @@ public class TbAlarmNodeTest { .endTs(ts) .tenantId(tenantId) .originator(originator) - .severity(CRITICAL) + .severity(AlarmSeverity.CRITICAL) .propagate(true) .type("SomeType") .details(null) @@ -139,16 +137,16 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Created")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -156,10 +154,10 @@ public class TbAlarmNodeTest { } @Test - public void buildDetailsThrowsException() throws ScriptException, IOException { + public void buildDetailsThrowsException() { initWithCreateAlarmScript(); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFailedFuture(new NotImplementedException("message"))); when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(null); @@ -179,10 +177,10 @@ public class TbAlarmNodeTest { } @Test - public void ifAlarmClearedCreateNew() throws ScriptException, IOException { + public void ifAlarmClearedCreateNew() { initWithCreateAlarmScript(); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long ts = msg.getTs(); Alarm clearedAlarm = Alarm.builder().cleared(true).acknowledged(true).build(); @@ -194,7 +192,7 @@ public class TbAlarmNodeTest { .endTs(ts) .tenantId(tenantId) .originator(originator) - .severity(CRITICAL) + .severity(AlarmSeverity.CRITICAL) .propagate(true) .type("SomeType") .details(null) @@ -213,16 +211,16 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Created")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); @@ -231,13 +229,13 @@ public class TbAlarmNodeTest { } @Test - public void alarmCanBeUpdated() throws IOException { + public void alarmCanBeUpdated() { initWithCreateAlarmScript(); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long oldEndDate = System.currentTimeMillis(); - Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(WARNING).endTs(oldEndDate).build(); + Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(AlarmSeverity.WARNING).endTs(oldEndDate).build(); when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(activeAlarm); @@ -245,7 +243,7 @@ public class TbAlarmNodeTest { Alarm expectedAlarm = Alarm.builder() .tenantId(tenantId) .originator(originator) - .severity(CRITICAL) + .severity(AlarmSeverity.CRITICAL) .propagate(true) .type("SomeType") .details(null) @@ -264,16 +262,16 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Updated")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_EXISTING_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_EXISTING_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -282,19 +280,19 @@ public class TbAlarmNodeTest { } @Test - public void alarmCanBeCleared() throws ScriptException, IOException { + public void alarmCanBeCleared() { initWithClearAlarmScript(); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long oldEndDate = System.currentTimeMillis(); - Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(WARNING).endTs(oldEndDate).build(); + Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(AlarmSeverity.WARNING).endTs(oldEndDate).build(); Alarm expectedAlarm = Alarm.builder() .tenantId(tenantId) .originator(originator) .cleared(true) - .severity(WARNING) + .severity(AlarmSeverity.WARNING) .propagate(false) .type("SomeType") .details(null) @@ -317,16 +315,16 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Cleared")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_CLEARED_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_CLEARED_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -337,18 +335,18 @@ public class TbAlarmNodeTest { public void alarmCanBeClearedWithAlarmOriginator() throws ScriptException, IOException { initWithClearAlarmScript(); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", alarmOriginator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, alarmOriginator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long oldEndDate = System.currentTimeMillis(); AlarmId id = new AlarmId(alarmOriginator.getId()); - Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(WARNING).endTs(oldEndDate).build(); + Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(AlarmSeverity.WARNING).endTs(oldEndDate).build(); activeAlarm.setId(id); Alarm expectedAlarm = Alarm.builder() .tenantId(tenantId) .originator(originator) .cleared(true) - .severity(WARNING) + .severity(AlarmSeverity.WARNING) .propagate(false) .type("SomeType") .details(null) @@ -372,16 +370,16 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Cleared")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(alarmOriginator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_CLEARED_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_CLEARED_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -410,14 +408,14 @@ public class TbAlarmNodeTest { String rawJson = "{\"alarmSeverity\": \"WARNING\", \"passed\": 5}"; metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long ts = msg.getTs(); Alarm expectedAlarm = Alarm.builder() .startTs(ts) .endTs(ts) .tenantId(tenantId) .originator(originator) - .severity(WARNING) + .severity(AlarmSeverity.WARNING) .propagate(true) .type("SomeType") .details(null) @@ -439,16 +437,16 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Created")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -476,14 +474,14 @@ public class TbAlarmNodeTest { node.init(ctx, nodeConfiguration); metaData.putValue("alarmSeverity", "WARNING"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long ts = msg.getTs(); Alarm expectedAlarm = Alarm.builder() .startTs(ts) .endTs(ts) .tenantId(tenantId) .originator(originator) - .severity(WARNING) + .severity(AlarmSeverity.WARNING) .propagate(true) .type("SomeType") .details(null) @@ -505,15 +503,15 @@ public class TbAlarmNodeTest { verify(ctx).tellNext(any(), eq("Created")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -525,7 +523,7 @@ public class TbAlarmNodeTest { for (int i = 0; i < 10; i++) { var config = new TbCreateAlarmNodeConfiguration(); config.setPropagateToTenant(true); - config.setSeverity(CRITICAL.name()); + config.setSeverity(AlarmSeverity.CRITICAL.name()); config.setAlarmType("SomeType" + i); config.setScriptLang(ScriptLanguage.JS); config.setAlarmDetailsBuildJs("DETAILS"); @@ -542,14 +540,14 @@ public class TbAlarmNodeTest { node.init(ctx, nodeConfiguration); metaData.putValue("key", "value"); - TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); long ts = msg.getTs(); Alarm expectedAlarm = Alarm.builder() .startTs(ts) .endTs(ts) .tenantId(tenantId) .originator(originator) - .severity(CRITICAL) + .severity(AlarmSeverity.CRITICAL) .propagateToTenant(true) .type("SomeType" + i) .details(null) @@ -570,16 +568,16 @@ public class TbAlarmNodeTest { verify(ctx, atMost(10)).tellNext(any(), eq("Created")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx, atMost(10)).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("value", metadataCaptor.getValue().getValue("key")); - assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); assertNotSame(metaData, metadataCaptor.getValue()); Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); @@ -591,7 +589,7 @@ public class TbAlarmNodeTest { try { TbCreateAlarmNodeConfiguration config = new TbCreateAlarmNodeConfiguration(); config.setPropagate(true); - config.setSeverity(CRITICAL.name()); + config.setSeverity(AlarmSeverity.CRITICAL.name()); config.setAlarmType("SomeType"); config.setScriptLang(ScriptLanguage.JS); config.setAlarmDetailsBuildJs("DETAILS"); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java index 542ca75a51..7a9d95cad4 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java @@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.action; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,12 +29,14 @@ import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; @@ -47,20 +48,16 @@ import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.relation.RelationService; import java.util.Collections; -import java.util.concurrent.Callable; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_CREATED; @RunWith(MockitoJUnitRunner.class) public class TbCreateRelationNodeTest { - private static final String RELATION_TYPE_CONTAINS = "Contains"; - private TbCreateRelationNode node; @Mock @@ -98,11 +95,11 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); - when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) + when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(EntityRelation.CONTAINS_TYPE), eq(RelationTypeGroup.COMMON))) .thenReturn(Futures.immediateFuture(false)); - when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, RELATION_TYPE_CONTAINS, RelationTypeGroup.COMMON)))) + when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)))) .thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); @@ -125,15 +122,15 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); EntityRelation relation = new EntityRelation(); - when(ctx.getRelationService().findByToAndTypeAsync(any(), eq(msg.getOriginator()), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) + when(ctx.getRelationService().findByToAndTypeAsync(any(), eq(msg.getOriginator()), eq(EntityRelation.CONTAINS_TYPE), eq(RelationTypeGroup.COMMON))) .thenReturn(Futures.immediateFuture(Collections.singletonList(relation))); when(ctx.getRelationService().deleteRelationAsync(any(), eq(relation))).thenReturn(Futures.immediateFuture(true)); - when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) + when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(EntityRelation.CONTAINS_TYPE), eq(RelationTypeGroup.COMMON))) .thenReturn(Futures.immediateFuture(false)); - when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, RELATION_TYPE_CONTAINS, RelationTypeGroup.COMMON)))) + when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)))) .thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); @@ -156,20 +153,17 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(ENTITY_CREATED.name(), deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); - when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON))) + when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(EntityRelation.CONTAINS_TYPE), eq(RelationTypeGroup.COMMON))) .thenReturn(Futures.immediateFuture(false)); - when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, RELATION_TYPE_CONTAINS, RelationTypeGroup.COMMON)))) + when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)))) .thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); - ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); - ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + verify(ctx).transformMsgOriginator(msgCaptor.capture(), originatorCaptor.capture()); assertEquals(assetId, originatorCaptor.getValue()); } @@ -188,9 +182,9 @@ public class TbCreateRelationNodeTest { private TbCreateRelationNodeConfiguration createRelationNodeConfig() { TbCreateRelationNodeConfiguration configuration = new TbCreateRelationNodeConfiguration(); configuration.setDirection(EntitySearchDirection.FROM.name()); - configuration.setRelationType(RELATION_TYPE_CONTAINS); + configuration.setRelationType(EntityRelation.CONTAINS_TYPE); configuration.setEntityCacheExpiration(300); - configuration.setEntityType("ASSET"); + configuration.setEntityType(EntityType.ASSET.name()); configuration.setEntityNamePattern("${name}"); configuration.setEntityTypePattern("${type}"); configuration.setCreateEntityIfNotExists(false); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java index f12288da3a..885002a694 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java @@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -49,7 +50,7 @@ public class TbLogNodeTest { TbLogNode node = new TbLogNode(); String data = "{\"key\": \"value\"}"; TbMsgMetaData metaData = new TbMsgMetaData(Map.of("mdKey1", "mdValue1", "mdKey2", "23")); - TbMsg msg = TbMsg.newMsg("POST_TELEMETRY", TenantId.SYS_TENANT_ID, metaData, data); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, metaData, data); String logMessage = node.toLogMessage(msg); log.info(logMessage); @@ -65,7 +66,7 @@ public class TbLogNodeTest { void givenEmptyDataMsg_whenToLog_thenReturnString() { TbLogNode node = new TbLogNode(); TbMsgMetaData metaData = new TbMsgMetaData(Collections.emptyMap()); - TbMsg msg = TbMsg.newMsg("POST_TELEMETRY", TenantId.SYS_TENANT_ID, metaData, ""); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, metaData, ""); String logMessage = node.toLogMessage(msg); log.info(logMessage); @@ -81,7 +82,7 @@ public class TbLogNodeTest { void givenNullDataMsg_whenToLog_thenReturnString() { TbLogNode node = new TbLogNode(); TbMsgMetaData metaData = new TbMsgMetaData(Collections.emptyMap()); - TbMsg msg = TbMsg.newMsg("POST_TELEMETRY", TenantId.SYS_TENANT_ID, metaData, null); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TenantId.SYS_TENANT_ID, metaData, null); String logMessage = node.toLogMessage(msg); log.info(logMessage); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java index f44c58e663..0c51462429 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.msg.TbMsg; @@ -48,18 +49,12 @@ import java.util.UUID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; -import static org.thingsboard.server.common.data.msg.TbMsgType.CONNECT_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.DISCONNECT_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @RunWith(MockitoJUnitRunner.class) public class TbMsgPushToEdgeNodeTest { - private static final List MISC_EVENTS = List.of(CONNECT_EVENT.name(), DISCONNECT_EVENT.name(), - ACTIVITY_EVENT.name(), INACTIVITY_EVENT.name()); + private static final List MISC_EVENTS = List.of(TbMsgType.CONNECT_EVENT, TbMsgType.DISCONNECT_EVENT, + TbMsgType.ACTIVITY_EVENT, TbMsgType.INACTIVITY_EVENT); TbMsgPushToEdgeNode node; @@ -89,8 +84,8 @@ public class TbMsgPushToEdgeNodeTest { Mockito.when(ctx.getEdgeService()).thenReturn(edgeService); Mockito.when(edgeService.findRelatedEdgeIdsByEntityId(tenantId, deviceId, new PageLink(TbMsgPushToEdgeNode.DEFAULT_PAGE_SIZE))).thenReturn(new PageData<>()); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), - TbMsgDataType.JSON, "{}", null, null); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, + TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, null, null); node.onMsg(ctx, msg); @@ -110,8 +105,8 @@ public class TbMsgPushToEdgeNodeTest { PageData edgePageData = new PageData<>(List.of(edgeId), 1, 1, false); Mockito.when(edgeService.findRelatedEdgeIdsByEntityId(tenantId, userId, new PageLink(TbMsgPushToEdgeNode.DEFAULT_PAGE_SIZE))).thenReturn(edgePageData); - TbMsg msg = TbMsg.newMsg(ATTRIBUTES_UPDATED.name(), userId, new TbMsgMetaData(), - TbMsgDataType.JSON, "{}", null, null); + TbMsg msg = TbMsg.newMsg(TbMsgType.ATTRIBUTES_UPDATED, userId, TbMsgMetaData.EMPTY, + TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, null, null); node.onMsg(ctx, msg); @@ -120,7 +115,7 @@ public class TbMsgPushToEdgeNodeTest { @Test public void testMiscEventsProcessedAsAttributesUpdated() { - for (String event : MISC_EVENTS) { + for (var event : MISC_EVENTS) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue(DataConstants.SCOPE, DataConstants.SERVER_SCOPE); testEvent(event, metaData, EdgeEventActionType.ATTRIBUTES_UPDATED, "kv"); @@ -129,12 +124,12 @@ public class TbMsgPushToEdgeNodeTest { @Test public void testMiscEventsProcessedAsTimeseriesUpdated() { - for (String event : MISC_EVENTS) { + for (var event : MISC_EVENTS) { testEvent(event, new TbMsgMetaData(), EdgeEventActionType.TIMESERIES_UPDATED, "data"); } } - private void testEvent(String event, TbMsgMetaData metaData, EdgeEventActionType expectedType, String dataKey) { + private void testEvent(TbMsgType event, TbMsgMetaData metaData, EdgeEventActionType expectedType, String dataKey) { Mockito.when(ctx.getTenantId()).thenReturn(tenantId); Mockito.when(ctx.getEdgeService()).thenReturn(edgeService); Mockito.when(ctx.getEdgeEventService()).thenReturn(edgeEventService); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java index 772d278ee8..785be659fd 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNodeTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -46,7 +47,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbAssetTypeSwitchNodeTest { @@ -118,7 +118,7 @@ class TbAssetTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java index 7bb8365895..a146b95ee1 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -44,7 +45,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbCheckAlarmStatusNodeTest { @@ -159,7 +159,7 @@ class TbCheckAlarmStatusNodeTest { } private TbMsg getTbMsg(String msgData) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, TbMsgMetaData.EMPTY, msgData); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, msgData); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java index 8926f36054..e69cc55d7e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckMessageNodeTest.java @@ -23,7 +23,9 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -38,15 +40,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE; -import static org.thingsboard.server.common.data.DataConstants.DEVICE_NAME; -import static org.thingsboard.server.common.data.DataConstants.DEVICE_TYPE; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbCheckMessageNodeTest { private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); - private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY); + private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); private TbCheckMessageNode node; @@ -193,12 +191,12 @@ class TbCheckMessageNodeTest { } private TbMsg getTbMsg(boolean emptyData) { - String data = emptyData ? TbMsg.EMPTY : "{\"temperature-0\": 25}"; + String data = emptyData ? TbMsg.EMPTY_JSON_OBJECT : "{\"temperature-0\": 25}"; var metadata = new TbMsgMetaData(); - metadata.putValue(DEVICE_NAME, "Test Device"); - metadata.putValue(DEVICE_TYPE, DEFAULT_DEVICE_TYPE); + metadata.putValue(DataConstants.DEVICE_NAME, "Test Device"); + metadata.putValue(DataConstants.DEVICE_TYPE, DataConstants.DEFAULT_DEVICE_TYPE); metadata.putValue("ts", String.valueOf(System.currentTimeMillis())); - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DEVICE_ID, metadata, data); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, DEVICE_ID, metadata, data); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java index 926d3b654b..bf7fa31f18 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeTest.java @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; @@ -54,14 +55,13 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbCheckRelationNodeTest { private static final TenantId TENANT_ID = new TenantId(UUID.randomUUID()); private static final DeviceId ORIGINATOR_ID = new DeviceId(UUID.randomUUID()); private static final TestDbCallbackExecutor DB_EXECUTOR = new TestDbCallbackExecutor(); - private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), ORIGINATOR_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY); + private static final TbMsg EMPTY_POST_ATTRIBUTES_MSG = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, ORIGINATOR_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); private TbCheckRelationNode node; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java index 3fe2e44f5d..a6f4a6e3cf 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNodeTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -46,7 +47,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbDeviceTypeSwitchNodeTest { @@ -118,6 +118,6 @@ class TbDeviceTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 2f75bbfe1b..6ce33fd009 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -27,10 +27,10 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.msg.TbNodeConnectionType; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -59,7 +59,7 @@ public class TbJsFilterNodeTest { @Test public void falseEvaluationDoNotSendMsg() throws TbNodeException { initWithScript(); - TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, new TbMsgMetaData(), TbMsgDataType.JSON, TbMsg.EMPTY, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, new TbMsgMetaData(), TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(false)); node.onMsg(ctx, msg); @@ -71,7 +71,7 @@ public class TbJsFilterNodeTest { public void exceptionInJsThrowsException() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, TbMsg.EMPTY, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFailedFuture(new ScriptException("error"))); @@ -83,7 +83,7 @@ public class TbJsFilterNodeTest { public void metadataConditionCanBeTrue() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = TbMsg.newMsg(EntityType.USER.name(), null, metaData, TbMsgDataType.JSON, TbMsg.EMPTY, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java index 763af2b1ee..3343c5fb92 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -58,7 +59,7 @@ public class TbJsSwitchNodeTest { metaData.putValue("humidity", "99"); String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); when(scriptEngine.executeSwitchAsync(msg)).thenReturn(Futures.immediateFuture(Sets.newHashSet("one", "three"))); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java index e79e2b77eb..4e2e5dc430 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeTest.java @@ -97,7 +97,7 @@ class TbMsgTypeFilterNodeTest { } private TbMsg getTbMsg(EntityId entityId, TbMsgType msgType) { - return TbMsg.newMsg(msgType.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY); + return TbMsg.newMsg(msgType, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java index cd4e21182f..c4fc8cd76d 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNodeTest.java @@ -88,7 +88,7 @@ class TbMsgTypeSwitchNodeTest { } private TbMsg getTbMsg(TbMsgType msgType) { - return TbMsg.newMsg(msgType.name(), DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY); + return TbMsg.newMsg(msgType, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java index 852552537f..3ed566b7e6 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeTest.java @@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -39,7 +40,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbOriginatorTypeFilterNodeTest { @@ -96,7 +96,7 @@ class TbOriginatorTypeFilterNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java index 64eb40fc41..796a7106fc 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNodeTest.java @@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -37,7 +38,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbOriginatorTypeSwitchNodeTest { @@ -90,7 +90,7 @@ class TbOriginatorTypeSwitchNodeTest { } private TbMsg getTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java index 1a01dda8c2..64a239d57f 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java @@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -40,9 +41,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.rule.engine.geo.GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER; -import static org.thingsboard.rule.engine.geo.GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; class TbGpsGeofencingFilterNodeTest { @@ -91,7 +89,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, - POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); // WHEN var exception = assertThrows(TbNodeException.class, () -> node.onMsg(ctx, msg)); @@ -109,7 +107,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, - POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); // WHEN var exception = assertThrows(TbNodeException.class, () -> node.onMsg(ctx, msg)); @@ -130,7 +128,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsgMetaData metadata = getMetadataForOldVersionPolygonPerimeter(); TbMsg msg = getTbMsg(deviceId, metadata, - POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); // WHEN node.onMsg(ctx, msg); @@ -154,7 +152,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsgMetaData metadata = getMetadataForOldVersionPolygonPerimeter(); TbMsg msg = getTbMsg(deviceId, metadata, - POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); // WHEN node.onMsg(ctx, msg); @@ -177,7 +175,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsgMetaData metadata = getMetadataForNewVersionPolygonPerimeter(); TbMsg msg = getTbMsg(deviceId, metadata, - POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); // WHEN node.onMsg(ctx, msg); @@ -200,7 +198,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsgMetaData metadata = getMetadataForNewVersionPolygonPerimeter(); TbMsg msg = getTbMsg(deviceId, metadata, - POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); // WHEN node.onMsg(ctx, msg); @@ -224,7 +222,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, - POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); // WHEN node.onMsg(ctx, msg); @@ -248,7 +246,7 @@ class TbGpsGeofencingFilterNodeTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); TbMsg msg = getTbMsg(deviceId, TbMsgMetaData.EMPTY, - POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); // WHEN node.onMsg(ctx, msg); @@ -450,11 +448,11 @@ class TbGpsGeofencingFilterNodeTest { private TbMsg getTbMsg(EntityId entityId, TbMsgMetaData metadata, double latitude, double longitude) { String data = "{\"latitude\": " + latitude + ", \"longitude\": " + longitude + "}"; - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, metadata, data); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, metadata, data); } private TbMsg getEmptyArrayTbMsg(EntityId entityId) { - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, TbMsgMetaData.EMPTY, "[]"); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, "[]"); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java index c95183717f..bf4d090c1c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -56,26 +57,26 @@ public class TbMsgToEmailNodeTest { private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); @Test - public void msgCanBeConverted() throws IOException { + public void msgCanBeConverted() { initWithScript(); metaData.putValue("username", "oreo"); metaData.putValue("userEmail", "user@email.io"); metaData.putValue("name", "temp"); metaData.putValue("passed", "5"); metaData.putValue("count", "100"); - TbMsg msg = TbMsg.newMsg( "USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); emailNode.onMsg(ctx, msg); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("SEND_EMAIL", typeCaptor.getValue()); + assertEquals(TbMsgType.SEND_EMAIL, typeCaptor.getValue()); assertEquals(originator, originatorCaptor.getValue()); assertEquals("oreo", metadataCaptor.getValue().getValue("username")); assertNotSame(metaData, metadataCaptor.getValue()); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java index 2efc438f8f..3e19c7a736 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java @@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.attributes.AttributesService; @@ -149,7 +150,7 @@ public class TbMathNodeTest { metaData.putValue("key2", "argumentA"); ObjectNode msgNode = JacksonUtil.newObjectNode() .put("key3", "argumentB").put("argumentA", 2).put("argumentB", 2); - TbMsg msg = TbMsg.newMsg("TEST", originator, metaData, msgNode.toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, msgNode.toString()); node.onMsg(ctx, msg); @@ -162,7 +163,7 @@ public class TbMathNodeTest { metaData.putValue("key2", "argumentC"); msgNode = JacksonUtil.newObjectNode() .put("key3", "argumentD").put("argumentC", 4).put("argumentD", 3); - msg = TbMsg.newMsg("TEST", originator, metaData, msgNode.toString()); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, msgNode.toString()); node.onMsg(ctx, msg); @@ -246,7 +247,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).put("b", arg2).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).put("b", arg2).toString()); node.onMsg(ctx, msg); @@ -269,7 +270,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).toString()); node.onMsg(ctx, msg); @@ -292,7 +293,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); node.onMsg(ctx, msg); @@ -315,7 +316,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); node.onMsg(ctx, msg); @@ -339,7 +340,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.TIME_SERIES, "b") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().toString()); Mockito.when(attributesService.find(tenantId, originator, DataConstants.SERVER_SCOPE, "a")) .thenReturn(Futures.immediateFuture(Optional.of(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("a", 2.0))))); @@ -367,7 +368,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); node.onMsg(ctx, msg); @@ -389,7 +390,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); node.onMsg(ctx, msg); @@ -411,7 +412,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); Mockito.when(telemetryService.saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble())) .thenReturn(Futures.immediateFuture(null)); @@ -437,7 +438,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) .thenReturn(Futures.immediateFuture(null)); @@ -462,7 +463,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) .thenReturn(Futures.immediateFuture(null)); @@ -493,7 +494,7 @@ public class TbMathNodeTest { new TbMathResult(TbMathArgumentType.MESSAGE_METADATA, "result", 3, false, false, null), tbMathArgument ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); node.onMsg(ctx, msg); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -513,7 +514,7 @@ public class TbMathNodeTest { new TbMathResult(TbMathArgumentType.TIME_SERIES, "result", 3, true, false, DataConstants.SERVER_SCOPE), new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); Throwable thrown = assertThrows(RuntimeException.class, () -> { node.onMsg(ctx, msg); }); @@ -527,7 +528,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), "[]"); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), "[]"); Throwable thrown = assertThrows(RuntimeException.class, () -> { node.onMsg(ctx, msg); }); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java index 6182d8cca5..9dbae201ac 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -63,8 +64,6 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class CalculateDeltaNodeTest { @@ -104,7 +103,7 @@ public class CalculateDeltaNodeTest { public void givenInvalidMsgType_whenOnMsg_thenShouldTellNextOther() { // GIVEN var msgData = "{\"pulseCounter\": 42}"; - var msg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -119,7 +118,7 @@ public class CalculateDeltaNodeTest { public void givenInvalidMsgDataType_whenOnMsg_thenShouldTellNextOther() { // GIVEN var msgData = "[]"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -134,7 +133,7 @@ public class CalculateDeltaNodeTest { @Test public void givenInputKeyIsNotPresent_whenOnMsg_thenShouldTellNextOther() { // GIVEN - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "{}"); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); // WHEN node.onMsg(ctxMock, msg); @@ -158,7 +157,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", 40.5))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -188,7 +187,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 40L))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -218,7 +217,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("temperature", "40.0"))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -252,7 +251,7 @@ public class CalculateDeltaNodeTest { var msgData = "{\"temperature\": 42,\"airPressure\":123}"; var firstMsgMetaData = new TbMsgMetaData(); firstMsgMetaData.putValue("ts", String.valueOf(3L)); - var firstMsg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, firstMsgMetaData, msgData); + var firstMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, firstMsgMetaData, msgData); // WHEN node.onMsg(ctxMock, firstMsg); @@ -276,7 +275,7 @@ public class CalculateDeltaNodeTest { var secondMsgMetaData = new TbMsgMetaData(); secondMsgMetaData.putValue("ts", String.valueOf(6L)); - var secondMsg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, secondMsgMetaData, msgData); + var secondMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, secondMsgMetaData, msgData); // WHEN node.onMsg(ctxMock, secondMsg); @@ -307,7 +306,7 @@ public class CalculateDeltaNodeTest { mockFindLatestAsync(new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", null))); var msgData = "{\"temperature\": 42,\"airPressure\":123}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -335,7 +334,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("pulseCounter", 200L))); var msgData = "{\"pulseCounter\":\"123\"}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN node.onMsg(ctxMock, msg); @@ -364,7 +363,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry("pulseCounter", "high"))); var msgData = "{\"pulseCounter\":\"123\"}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN-THEN Assertions.assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) @@ -378,7 +377,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry("pulseCounter", false))); var msgData = "{\"pulseCounter\":true}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN-THEN Assertions.assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) @@ -392,7 +391,7 @@ public class CalculateDeltaNodeTest { mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new JsonDataEntry("pulseCounter", "{\"isActive\":false}"))); var msgData = "{\"pulseCounter\":{\"isActive\":true}}"; - var msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); // WHEN-THEN Assertions.assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java index dfd054edc7..5de25ebfc2 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNodeTest.java @@ -31,7 +31,9 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -51,8 +53,6 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.common.data.security.DeviceCredentialsType.ACCESS_TOKEN; @ExtendWith(MockitoExtension.class) public class TbFetchDeviceCredentialsNodeTest { @@ -98,7 +98,7 @@ public class TbFetchDeviceCredentialsNodeTest { doReturn(deviceCredentialsServiceMock).when(ctxMock).getDeviceCredentialsService(); doAnswer(invocation -> { DeviceCredentials deviceCredentials = new DeviceCredentials(); - deviceCredentials.setCredentialsType(ACCESS_TOKEN); + deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); return deviceCredentials; }).when(deviceCredentialsServiceMock).findDeviceCredentialsByDeviceId(any(), any()); doAnswer(invocation -> JacksonUtil.newObjectNode()).when(deviceCredentialsServiceMock).toCredentialsInfo(any()); @@ -172,7 +172,7 @@ public class TbFetchDeviceCredentialsNodeTest { final var metaData = new TbMsgMetaData(mdMap); final String data = "{\"TestAttribute_1\": \"humidity\", \"TestAttribute_2\": \"voltage\"}"; - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, metaData, data, callbackMock); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, metaData, data, callbackMock); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java index cb36982b20..a889f91b77 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java @@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -240,7 +241,7 @@ public class TbGetAttributesNodeTest { public void givenFetchLatestTimeseriesToDataAndDataIsNotJsonObject_whenOnMsg_thenException() throws Exception { // GIVEN node = initNode(FetchTo.DATA, true, true); - var msg = TbMsg.newMsg("TEST", ORIGINATOR, new TbMsgMetaData(), "[]"); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -369,7 +370,7 @@ public class TbGetAttributesNodeTest { msgMetaData.putValue("client_attr_metadata", "client_attr_3"); msgMetaData.putValue("server_attr_metadata", "server_attr_3"); - return TbMsg.newMsg("TEST", entityId, msgMetaData, JacksonUtil.toString(msgData)); + return TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, entityId, msgMetaData, JacksonUtil.toString(msgData)); } private List getAttributeNames(String prefix) { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index 12385f6e28..3a8f36f5dc 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -33,6 +33,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; @@ -47,6 +48,7 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -74,8 +76,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetCustomerAttributeNodeTest { @@ -208,7 +208,7 @@ public class TbGetCustomerAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -223,7 +223,7 @@ public class TbGetCustomerAttributeNodeTest { // GIVEN var userId = new UserId(UUID.randomUUID()); - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), userId, new TbMsgMetaData(), "{}"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, userId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); when(ctxMock.getTenantId()).thenReturn(TENANT_ID); @@ -276,7 +276,7 @@ public class TbGetCustomerAttributeNodeTest { doReturn(device).when(deviceServiceMock).findDeviceById(eq(TENANT_ID), eq(device.getId())); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributesList)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -323,7 +323,7 @@ public class TbGetCustomerAttributeNodeTest { doReturn(Futures.immediateFuture(user)).when(userServiceMock).findUserByIdAsync(eq(TENANT_ID), eq(user.getId())); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(CUSTOMER_ID), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributesList)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -467,7 +467,7 @@ public class TbGetCustomerAttributeNodeTest { var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index 540a260338..8b958bcc3c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -68,7 +69,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetCustomerDetailsNodeTest { @@ -157,7 +157,7 @@ public class TbGetCustomerDetailsNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -456,7 +456,7 @@ public class TbGetCustomerDetailsNodeTest { var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, msgMetaData, msgData); } private void mockFindCustomer() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index addd05bd0e..0057ec7c61 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -51,7 +52,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetOriginatorFieldsNodeTest { @@ -133,7 +133,7 @@ public class TbGetOriginatorFieldsNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -162,7 +162,7 @@ public class TbGetOriginatorFieldsNodeTest { node.fetchTo = FetchTo.DATA; var msgMetaData = new TbMsgMetaData(); var msgData = "{\"temp\":42,\"humidity\":77}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -204,7 +204,7 @@ public class TbGetOriginatorFieldsNodeTest { node.fetchTo = FetchTo.DATA; var msgMetaData = new TbMsgMetaData(); var msgData = "{\"temp\":42,\"humidity\":77}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -247,7 +247,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -295,7 +295,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); when(ctxMock.getDeviceService()).thenReturn(deviceServiceMock); when(ctxMock.getTenantId()).thenReturn(DUMMY_TENANT_ID); @@ -353,7 +353,7 @@ public class TbGetOriginatorFieldsNodeTest { "testKey1", "testValue1", "testKey2", "123")); var msgData = "[\"value1\",\"value2\"]"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), new DashboardId(UUID.randomUUID()), msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, new DashboardId(UUID.randomUUID()), msgMetaData, msgData); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java index 1a638eca39..6b3a225eac 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java @@ -35,6 +35,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.data.RelationsQuery; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; @@ -53,6 +54,7 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; @@ -83,8 +85,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetRelatedAttributeNodeTest { @@ -222,7 +222,7 @@ public class TbGetRelatedAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -290,7 +290,7 @@ public class TbGetRelatedAttributeNodeTest { doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(user.getId()), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(user.getId()), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributes)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -341,7 +341,7 @@ public class TbGetRelatedAttributeNodeTest { doReturn(Futures.immediateFuture(List.of(entityRelation))).when(relationServiceMock).findByQuery(eq(TENANT_ID), any()); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(secondCustomer.getId()), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(secondCustomer.getId()), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributes)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -591,7 +591,7 @@ public class TbGetRelatedAttributeNodeTest { msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; } - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java index c163d68733..ffb053c93a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java @@ -31,6 +31,7 @@ import org.thingsboard.rule.engine.TestDbCallbackExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; @@ -41,6 +42,7 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -61,8 +63,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetTenantAttributeNodeTest { @@ -188,7 +188,7 @@ public class TbGetTenantAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -215,7 +215,7 @@ public class TbGetTenantAttributeNodeTest { when(ctxMock.getTenantId()).thenReturn(TENANT_ID); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributesList)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -256,7 +256,7 @@ public class TbGetTenantAttributeNodeTest { when(ctxMock.getTenantId()).thenReturn(TENANT_ID); when(ctxMock.getAttributesService()).thenReturn(attributesServiceMock); - when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) + when(attributesServiceMock.find(eq(TENANT_ID), eq(TENANT_ID), eq(DataConstants.SERVER_SCOPE), argThat(new ListMatcher<>(expectedPatternProcessedKeysList)))) .thenReturn(Futures.immediateFuture(attributesList)); when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); @@ -396,7 +396,7 @@ public class TbGetTenantAttributeNodeTest { var msgData = "{\"temp\":42,\"humidity\":77,\"messageBodyPattern1\":\"targetKey2\",\"messageBodyPattern2\":\"sourceKey3\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), originator, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, msgMetaData, msgData); } @RequiredArgsConstructor diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java index 430a772269..2dda9ffaf0 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java @@ -32,6 +32,7 @@ import org.thingsboard.rule.engine.util.ContactBasedEntityDetails; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -49,7 +50,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @ExtendWith(MockitoExtension.class) public class TbGetTenantDetailsNodeTest { @@ -127,7 +127,7 @@ public class TbGetTenantDetailsNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg("SOME_MESSAGE_TYPE", DUMMY_DEVICE_ORIGINATOR, new TbMsgMetaData(), "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); @@ -287,7 +287,7 @@ public class TbGetTenantDetailsNodeTest { var msgData = "{\"dataKey1\":123,\"dataKey2\":\"dataValue2\"}"; - msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, msgMetaData, msgData); } private void mockFindTenant() { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java index 0d94d9de16..6bc4a59610 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.EntityKeyValueType; import org.thingsboard.server.common.data.query.FilterPredicateValue; @@ -62,10 +63,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_CLEAR; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; -import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class DeviceStateTest { @@ -94,9 +91,10 @@ public class DeviceStateTest { }); when(ctx.getAlarmService()).thenReturn(alarmService); - when(ctx.newMsg(any(), any(), any(), any(), any(), any())).thenAnswer(invocationOnMock -> { + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), any())).thenAnswer(invocationOnMock -> { + TbMsgType type = invocationOnMock.getArgument(1); String data = invocationOnMock.getArgument(invocationOnMock.getArguments().length - 1); - return TbMsg.newMsg(null, null, new TbMsgMetaData(), data); + return TbMsg.newMsg(type, null, TbMsgMetaData.EMPTY, data); }); } @@ -108,7 +106,7 @@ public class DeviceStateTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); DeviceState deviceState = createDeviceState(deviceId, alarmConfig); - TbMsg attributeUpdateMsg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), + TbMsg attributeUpdateMsg = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, deviceId, new TbMsgMetaData(), "{ \"enabled\": false }"); deviceState.process(ctx, attributeUpdateMsg); @@ -117,11 +115,11 @@ public class DeviceStateTest { verify(ctx).enqueueForTellNext(resultMsgCaptor.capture(), eq("Alarm Created")); Alarm alarm = JacksonUtil.fromString(resultMsgCaptor.getValue().getData(), Alarm.class); - deviceState.process(ctx, TbMsg.newMsg(ALARM_CLEAR.name(), deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); + deviceState.process(ctx, TbMsg.newMsg(TbMsgType.ALARM_CLEAR, deviceId, TbMsgMetaData.EMPTY, JacksonUtil.toString(alarm))); reset(ctx); String deletedAttributes = "{ \"attributes\": [ \"other\" ] }"; - deviceState.process(ctx, TbMsg.newMsg(ATTRIBUTES_DELETED.name(), deviceId, new TbMsgMetaData(), deletedAttributes)); + deviceState.process(ctx, TbMsg.newMsg(TbMsgType.ATTRIBUTES_DELETED, deviceId, new TbMsgMetaData(), deletedAttributes)); verify(ctx, never()).enqueueForTellNext(any(), anyString()); } @@ -131,17 +129,17 @@ public class DeviceStateTest { DeviceId deviceId = new DeviceId(UUID.randomUUID()); DeviceState deviceState = createDeviceState(deviceId, alarmConfig); - TbMsg attributeUpdateMsg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), - deviceId, new TbMsgMetaData(), "{ \"enabled\": false }"); + TbMsg attributeUpdateMsg = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, + deviceId, TbMsgMetaData.EMPTY, "{ \"enabled\": false }"); deviceState.process(ctx, attributeUpdateMsg); ArgumentCaptor resultMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx).enqueueForTellNext(resultMsgCaptor.capture(), eq("Alarm Created")); Alarm alarm = JacksonUtil.fromString(resultMsgCaptor.getValue().getData(), Alarm.class); - deviceState.process(ctx, TbMsg.newMsg(ALARM_CLEAR.name(), deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm))); + deviceState.process(ctx, TbMsg.newMsg(TbMsgType.ALARM_CLEAR, deviceId, TbMsgMetaData.EMPTY, JacksonUtil.toString(alarm))); - TbMsg alarmDeleteNotification = TbMsg.newMsg(ALARM_DELETE.name(), deviceId, new TbMsgMetaData(), JacksonUtil.toString(alarm)); + TbMsg alarmDeleteNotification = TbMsg.newMsg(TbMsgType.ALARM_DELETE, deviceId, TbMsgMetaData.EMPTY, JacksonUtil.toString(alarm)); assertDoesNotThrow(() -> { deviceState.process(ctx, alarmDeleteNotification); }); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index 20c529646c..03a9c17a60 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -54,6 +54,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValueSourceType; @@ -85,7 +86,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @RunWith(MockitoJUnitRunner.class) public class TbDeviceProfileNodeTest { @@ -122,8 +122,8 @@ public class TbDeviceProfileNodeTest { Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 42); - TbMsg msg = TbMsg.newMsg("123456789", deviceId, new TbMsgMetaData(), - TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); + TbMsg msg = TbMsg.newMsg("123456789", deviceId, TbMsgMetaData.EMPTY, + TbMsgDataType.JSON, JacksonUtil.toString(data)); node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); @@ -141,7 +141,7 @@ public class TbDeviceProfileNodeTest { Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 42); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); @@ -193,25 +193,25 @@ public class TbDeviceProfileNodeTest { Mockito.when(alarmService.findLatestActiveByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")).thenReturn(null); registerCreateAlarmMock(alarmService.createAlarm(any()), true); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())).thenReturn(theMsg); + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())).thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 42); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); - TbMsg theMsg2 = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "2"); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())).thenReturn(theMsg2); + TbMsg theMsg2 = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, "2"); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())).thenReturn(theMsg2); registerCreateAlarmMock(alarmService.updateAlarm(any()), false); - TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); verify(ctx).tellSuccess(msg2); @@ -286,13 +286,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(attrListListenableFuture); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + Mockito.when(ctx.newMsg(Mockito.any(), Mockito.any(TbMsgType.class), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 21); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -373,13 +373,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString())) .thenReturn(attrListListenableFuture); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 21); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -442,13 +442,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFutureWithLess); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -536,13 +536,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFuture); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -557,7 +557,7 @@ public class TbDeviceProfileNodeTest { Thread.sleep(halfOfAlarmDelay); - TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -660,13 +660,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listNoDurationAttribute); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -681,7 +681,7 @@ public class TbDeviceProfileNodeTest { Thread.sleep(halfOfAlarmDelay); - TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -769,13 +769,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFuture); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -784,7 +784,7 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); data.put("temperature", 151); - TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -885,13 +885,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listNoDurationAttribute); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -900,7 +900,7 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); data.put("temperature", 151); - TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -981,13 +981,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFuture); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1002,7 +1002,7 @@ public class TbDeviceProfileNodeTest { Thread.sleep(halfOfAlarmDelay); - TbMsg msg2 = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg2 = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg2); @@ -1079,13 +1079,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFuture); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1161,13 +1161,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFutureActiveSchedule); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); // Mockito.reset(ctx); @@ -1257,11 +1257,11 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) .thenReturn(listListenableFutureInactiveSchedule); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 35); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1335,13 +1335,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) .thenReturn(optionalListenableFutureWithLess); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 25); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1408,13 +1408,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) .thenReturn(optionalListenableFutureWithLess); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 40); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1491,13 +1491,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) .thenReturn(optionalListenableFutureWithLess); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150L); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); @@ -1576,13 +1576,13 @@ public class TbDeviceProfileNodeTest { Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) .thenReturn(optionalListenableFutureWithLess); - TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + TbMsg theMsg = TbMsg.newMsg(TbMsgType.ALARM, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + when(ctx.newMsg(any(), any(TbMsgType.class), any(), any(), any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = JacksonUtil.newObjectNode(); data.put("temperature", 150L); - TbMsg msg = TbMsg.newMsg(POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, JacksonUtil.toString(data), null, null); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java index 7b6a54ae0b..2ddc20d5ed 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java @@ -32,6 +32,7 @@ import org.springframework.web.client.AsyncRestTemplate; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -144,16 +145,15 @@ public class TbHttpClientTest { var httpClient = new TbHttpClient(config, eventLoop); httpClient.setHttpClient(asyncRestTemplate); - var msg = TbMsg.newMsg("GET", new DeviceId(EntityId.NULL_UUID), TbMsgMetaData.EMPTY, "{}"); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, new DeviceId(EntityId.NULL_UUID), TbMsgMetaData.EMPTY, "{}"); var successMsg = TbMsg.newMsg( - "SUCCESS", msg.getOriginator(), + TbMsgType.POST_TELEMETRY_REQUEST, msg.getOriginator(), msg.getMetaData(), msg.getData() ); var ctx = mock(TbContext.class); when(ctx.transformMsg( - eq(msg), eq(msg.getType()), - eq(msg.getOriginator()), + eq(msg), eq(msg.getMetaData()), eq(msg.getData()) )).thenReturn(successMsg); @@ -161,15 +161,14 @@ public class TbHttpClientTest { var capturedData = ArgumentCaptor.forClass(String.class); when(ctx.transformMsg( - eq(msg), eq(msg.getType()), - eq(msg.getOriginator()), + eq(msg), any(), capturedData.capture() )).thenReturn(successMsg); httpClient.processMessage(ctx, msg, m -> ctx.tellSuccess(msg), - (m, t) -> ctx.tellFailure(m, t)); + ctx::tellFailure); Awaitility.await() .atMost(30, TimeUnit.SECONDS) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java index 9185a31118..705f298513 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java @@ -16,7 +16,6 @@ package org.thingsboard.rule.engine.rest; import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; @@ -39,6 +38,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -136,23 +136,18 @@ public class TbRestApiCallNodeTest { config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path)); initWithConfig(config); - TbMsg msg = TbMsg.newMsg( "USER", originator, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); restNode.onMsg(ctx, msg); assertTrue("Server handled request", latch.await(10, TimeUnit.SECONDS)); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); - ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + verify(ctx).transformMsg(msgCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - - assertEquals("USER", typeCaptor.getValue()); - assertEquals(originator, originatorCaptor.getValue()); assertNotSame(metaData, metadataCaptor.getValue()); - assertEquals("{}", dataCaptor.getValue()); + assertEquals(TbMsg.EMPTY_JSON_OBJECT, dataCaptor.getValue()); } @Test @@ -202,22 +197,18 @@ public class TbRestApiCallNodeTest { config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path)); initWithConfig(config); - TbMsg msg = TbMsg.newMsg( "USER", originator, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); restNode.onMsg(ctx, msg); assertTrue("Server handled request", latch.await(10, TimeUnit.SECONDS)); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); - ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + verify(ctx).transformMsg(msgCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("USER", typeCaptor.getValue()); - assertEquals(originator, originatorCaptor.getValue()); assertNotSame(metaData, metadataCaptor.getValue()); - assertEquals("{}", dataCaptor.getValue()); + assertEquals(TbMsg.EMPTY_JSON_OBJECT, dataCaptor.getValue()); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java index a72d343d10..7b12dbb2b1 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java @@ -80,7 +80,7 @@ public class TbSendRPCReplyNodeTest { Mockito.when(ctx.getRpcService()).thenReturn(rpcService); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, getDefaultMetadata(), + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, getDefaultMetadata(), TbMsgDataType.JSON, DUMMY_DATA, null, null); node.onMsg(ctx, msg); @@ -99,7 +99,7 @@ public class TbSendRPCReplyNodeTest { TbMsgMetaData defaultMetadata = getDefaultMetadata(); defaultMetadata.putValue(DataConstants.EDGE_ID, UUID.randomUUID().toString()); defaultMetadata.putValue(DataConstants.DEVICE_ID, UUID.randomUUID().toString()); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetadata, + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, deviceId, defaultMetadata, TbMsgDataType.JSON, DUMMY_DATA, null, null); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java index 9b23a3aa31..632243676f 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java @@ -25,7 +25,9 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -49,11 +51,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_METADATA_KEY; -import static org.thingsboard.server.common.data.DataConstants.SCOPE; -import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; -import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; @Slf4j public class TbMsgDeleteAttributesNodeTest { @@ -95,7 +92,7 @@ public class TbMsgDeleteAttributesNodeTest { @Test void givenDefaultConfig_whenVerify_thenOK() { TbMsgDeleteAttributesNodeConfiguration defaultConfig = new TbMsgDeleteAttributesNodeConfiguration().defaultConfiguration(); - assertThat(defaultConfig.getScope()).isEqualTo(SERVER_SCOPE); + assertThat(defaultConfig.getScope()).isEqualTo(DataConstants.SERVER_SCOPE); assertThat(defaultConfig.getKeys()).isEqualTo(Collections.emptyList()); } @@ -116,7 +113,7 @@ public class TbMsgDeleteAttributesNodeTest { void givenMsg_whenOnMsg_thenVerifyOutput_SendAttributesDeletedNotification_NotifyDevice() throws Exception { config.setSendAttributesDeletedNotification(true); config.setNotifyDevice(true); - config.setScope(SHARED_SCOPE); + config.setScope(DataConstants.SHARED_SCOPE); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); onMsg_thenVerifyOutput(true, true, false); @@ -136,12 +133,12 @@ public class TbMsgDeleteAttributesNodeTest { ); TbMsgMetaData metaData = new TbMsgMetaData(mdMap); if (notifyDeviceMetadata) { - metaData.putValue(NOTIFY_DEVICE_METADATA_KEY, "true"); - metaData.putValue(SCOPE, SHARED_SCOPE); + metaData.putValue(DataConstants.NOTIFY_DEVICE_METADATA_KEY, "true"); + metaData.putValue(DataConstants.SCOPE, DataConstants.SHARED_SCOPE); } final String data = "{\"TestAttribute_2\": \"humidity\", \"TestAttribute_3\": \"voltage\"}"; - TbMsg msg = TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), deviceId, metaData, data, callback); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, deviceId, metaData, data, callback); node.onMsg(ctx, msg); ArgumentCaptor successCaptor = ArgumentCaptor.forClass(Runnable.class); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java index 071ae9ce96..8ea7806ae2 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -79,7 +80,7 @@ public class TbChangeOriginatorNodeTest { RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - TbMsg msg = TbMsg.newMsg( "ASSET", assetId, new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, assetId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(ctx.getAssetService()).thenReturn(assetService); when(assetService.findAssetByIdAsync(any(),eq( assetId))).thenReturn(Futures.immediateFuture(asset)); @@ -87,11 +88,8 @@ public class TbChangeOriginatorNodeTest { node.onMsg(ctx, msg); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); - ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); - ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + verify(ctx).transformMsgOriginator(msgCaptor.capture(), originatorCaptor.capture()); assertEquals(customerId, originatorCaptor.getValue()); } @@ -107,18 +105,15 @@ public class TbChangeOriginatorNodeTest { RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - TbMsg msg = TbMsg.newMsg( "ASSET", assetId, new TbMsgMetaData(), TbMsgDataType.JSON,"{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, assetId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON,TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(ctx.getAssetService()).thenReturn(assetService); when(assetService.findAssetByIdAsync(any(), eq(assetId))).thenReturn(Futures.immediateFuture(asset)); node.onMsg(ctx, msg); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); - ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); - ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + verify(ctx).transformMsgOriginator(msgCaptor.capture(), originatorCaptor.capture()); assertEquals(customerId, originatorCaptor.getValue()); } @@ -134,7 +129,7 @@ public class TbChangeOriginatorNodeTest { RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - TbMsg msg = TbMsg.newMsg( "ASSET", assetId, new TbMsgMetaData(), TbMsgDataType.JSON,"{}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, assetId, TbMsgMetaData.EMPTY, TbMsgDataType.JSON,TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(ctx.getAssetService()).thenReturn(assetService); when(assetService.findAssetByIdAsync(any(), eq(assetId))).thenReturn(Futures.immediateFuture(null)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java index a2bfb26b4e..5d8901dcad 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java @@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -42,7 +43,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbCopyKeysNodeTest { DeviceId deviceId; @@ -158,7 +158,7 @@ public class TbCopyKeysNodeTest { "voltageDataValue", "220", "city", "NY" ); - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java index c73e1eec15..838f35e25e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java @@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -42,7 +43,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbDeleteKeysNodeTest { DeviceId deviceId; @@ -141,7 +141,7 @@ public class TbDeleteKeysNodeTest { "voltageDataValue", "220", "city", "NY" ); - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java index 91db8dfd26..a86d902007 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java @@ -27,6 +27,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -42,7 +43,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbJsonPathNodeTest { DeviceId deviceId; @@ -171,6 +171,6 @@ public class TbJsonPathNodeTest { Map mdMap = Map.of("country", "US", "city", "NY" ); - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java index ba17187297..be6aea6fa5 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbMsgDeduplicationNodeTest.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -65,14 +66,10 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; @Slf4j public class TbMsgDeduplicationNodeTest { - private static final String TB_MSG_DEDUPLICATION_TIMEOUT_MSG = "TbMsgDeduplicationNodeMsg"; - private TbContext ctx; private final ThingsBoardThreadFactory factory = ThingsBoardThreadFactory.forName("de-duplication-node-test"); @@ -98,12 +95,12 @@ public class TbMsgDeduplicationNodeTest { when(ctx.getTenantId()).thenReturn(tenantId); doAnswer((Answer) invocationOnMock -> { - String type = (String) (invocationOnMock.getArguments())[1]; + TbMsgType type = (TbMsgType) (invocationOnMock.getArguments())[1]; EntityId originator = (EntityId) (invocationOnMock.getArguments())[2]; TbMsgMetaData metaData = (TbMsgMetaData) (invocationOnMock.getArguments())[3]; String data = (String) (invocationOnMock.getArguments())[4]; return TbMsg.newMsg(type, originator, metaData.copy(), data); - }).when(ctx).newMsg(isNull(), eq(TB_MSG_DEDUPLICATION_TIMEOUT_MSG), nullable(EntityId.class), any(TbMsgMetaData.class), any(String.class)); + }).when(ctx).newMsg(isNull(), eq(TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG), nullable(EntityId.class), any(TbMsgMetaData.class), any(String.class)); node = spy(new TbMsgDeduplicationNode()); config = new TbMsgDeduplicationNodeConfiguration().defaultConfiguration(); } @@ -243,7 +240,7 @@ public class TbMsgDeduplicationNodeTest { config.setInterval(deduplicationInterval); config.setStrategy(DeduplicationStrategy.ALL); - config.setOutMsgType(POST_ATTRIBUTES_REQUEST.name()); + config.setOutMsgType(TbMsgType.POST_ATTRIBUTES_REQUEST.name()); config.setQueueName(DataConstants.HP_QUEUE_NAME); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -283,7 +280,7 @@ public class TbMsgDeduplicationNodeTest { config.setInterval(deduplicationInterval); config.setStrategy(DeduplicationStrategy.ALL); - config.setOutMsgType(POST_ATTRIBUTES_REQUEST.name()); + config.setOutMsgType(TbMsgType.POST_ATTRIBUTES_REQUEST.name()); config.setQueueName(DataConstants.HP_QUEUE_NAME); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -415,7 +412,7 @@ public class TbMsgDeduplicationNodeTest { metaData.putValue("ts", String.valueOf(ts)); return TbMsg.newMsg( DataConstants.MAIN_QUEUE_NAME, - POST_TELEMETRY_REQUEST.name(), + TbMsgType.POST_TELEMETRY_REQUEST, deviceId, metaData, JacksonUtil.toString(dataNode)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java index 0caa7f74f3..f226f2e166 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java @@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -40,7 +41,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbRenameKeysNodeTest { DeviceId deviceId; @@ -155,6 +155,6 @@ public class TbRenameKeysNodeTest { "country", "US", "city", "NY" ); - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java index cf1eee085c..5cdb0d305b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java @@ -27,6 +27,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -43,7 +44,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; public class TbSplitArrayMsgNodeTest { DeviceId deviceId; @@ -133,6 +133,6 @@ public class TbSplitArrayMsgNodeTest { "country", "US", "city", "NY" ); - return TbMsg.newMsg(POST_ATTRIBUTES_REQUEST.name(), entityId, new TbMsgMetaData(mdMap), data, callback); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, new TbMsgMetaData(mdMap), data, callback); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java index 71348a1a23..56c94fd4ff 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -60,8 +61,8 @@ public class TbTransformMsgNodeTest { RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON,rawJson, ruleChainId, ruleNodeId); - TbMsg transformedMsg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, "{new}", ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, metaData, TbMsgDataType.JSON,rawJson, ruleChainId, ruleNodeId); + TbMsg transformedMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, metaData, TbMsgDataType.JSON, "{new}", ruleChainId, ruleNodeId); when(scriptEngine.executeUpdateAsync(msg)).thenReturn(Futures.immediateFuture(Collections.singletonList(transformedMsg))); node.onMsg(ctx, msg); @@ -80,7 +81,7 @@ public class TbTransformMsgNodeTest { RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); when(scriptEngine.executeUpdateAsync(msg)).thenReturn(Futures.immediateFailedFuture(new IllegalStateException("error"))); node.onMsg(ctx, msg); From 4528348143b67e14d502069cac0d663d72ecdfac Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 6 Jul 2023 13:18:02 +0300 Subject: [PATCH 024/166] replaced new TbMsgMetaData() with TbMsgMetaData.EMPTY and added additional refactoring after review of changes --- .../server/common/data/msg/TbMsgType.java | 6 ---- .../server/common/data/msg/TbMsgTypeTest.java | 1 - .../rule/engine/debug/TbMsgGeneratorNode.java | 2 +- .../engine/metadata/TbGetTelemetryNode.java | 12 ++++--- .../rule/engine/profile/DeviceState.java | 31 ++++++------------- .../engine/profile/TbDeviceProfileNode.java | 2 +- .../action/TbCreateRelationNodeTest.java | 2 +- .../engine/edge/TbMsgPushToEdgeNodeTest.java | 2 +- .../engine/filter/TbJsFilterNodeTest.java | 2 +- .../geo/TbGpsGeofencingFilterNodeTest.java | 2 +- .../rule/engine/math/TbMathNodeTest.java | 26 ++++++++-------- .../metadata/CalculateDeltaNodeTest.java | 3 +- .../metadata/TbGetAttributesNodeTest.java | 2 +- .../TbGetCustomerAttributeNodeTest.java | 2 +- .../TbGetCustomerDetailsNodeTest.java | 2 +- .../TbGetOriginatorFieldsNodeTest.java | 2 +- .../TbGetRelatedAttributeNodeTest.java | 2 +- .../TbGetTenantAttributeNodeTest.java | 2 +- .../metadata/TbGetTenantDetailsNodeTest.java | 2 +- .../rule/engine/profile/DeviceStateTest.java | 4 +-- .../rule/engine/rest/TbHttpClientTest.java | 2 +- .../engine/transform/TbCopyKeysNodeTest.java | 6 ++-- .../transform/TbDeleteKeysNodeTest.java | 3 +- .../transform/TbRenameKeysNodeTest.java | 3 +- .../transform/TbSplitArrayMsgNodeTest.java | 3 +- 25 files changed, 52 insertions(+), 74 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java index 0084b391eb..206b203682 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java @@ -71,19 +71,13 @@ public enum TbMsgType { // tellSelfOnly types GENERATOR_NODE_SELF_MSG(null, true), - DEVICE_PROFILE_PERIODIC_SELF_MSG(null, true), DEVICE_PROFILE_UPDATE_SELF_MSG(null, true), DEVICE_UPDATE_SELF_MSG(null, true), - DEDUPLICATION_TIMEOUT_SELF_MSG(null, true), - DELAY_TIMEOUT_SELF_MSG(null, true), - MSG_COUNT_SELF_MSG(null, true); - - public static final List NODE_CONNECTIONS = EnumSet.allOf(TbMsgType.class).stream() .filter(tbMsgType -> !tbMsgType.isTellSelfOnly()) .map(TbMsgType::getRuleNodeConnection) diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java index 58f2089aed..1323b7359d 100644 --- a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java +++ b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java @@ -54,7 +54,6 @@ class TbMsgTypeTest { MSG_COUNT_SELF_MSG ); - // backward-compatibility tests @Test diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 7c3d6424e5..2f1aae5000 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -136,7 +136,7 @@ public class TbMsgGeneratorNode implements TbNode { } lastScheduledTs = lastScheduledTs + delay; long curDelay = Math.max(0L, (lastScheduledTs - curTs)); - TbMsg tickMsg = ctx.newMsg(config.getQueueName(), TbMsgType.GENERATOR_NODE_SELF_MSG, ctx.getSelfId(), new TbMsgMetaData(), TbMsg.EMPTY_STRING); + TbMsg tickMsg = ctx.newMsg(config.getQueueName(), TbMsgType.GENERATOR_NODE_SELF_MSG, ctx.getSelfId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); nextTickId = tickMsg.getId(); ctx.tellSelf(tickMsg, curDelay); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java index 4cf564fd7b..a7ddf2a76b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; import java.util.List; import java.util.concurrent.ExecutionException; @@ -98,8 +99,8 @@ public class TbGetTelemetryNode implements TbNode { List keys = TbNodeUtils.processPatterns(tsKeyNames, msg); ListenableFuture> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(interval, keys)); DonAsynchron.withCallback(list, data -> { - process(data, msg, keys); - ctx.tellSuccess(msg); + var metaData = updateMetadata(data, msg, keys); + ctx.tellSuccess(TbMsg.transformMsg(msg, metaData)); }, error -> ctx.tellFailure(msg, error), ctx.getDbCallbackExecutor()); } catch (Exception e) { ctx.tellFailure(msg, e); @@ -129,19 +130,20 @@ public class TbGetTelemetryNode implements TbNode { } } - private void process(List entries, TbMsg msg, List keys) { + private TbMsgMetaData updateMetadata(List entries, TbMsg msg, List keys) { ObjectNode resultNode = JacksonUtil.newObjectNode(JacksonUtil.ALLOW_UNQUOTED_FIELD_NAMES_MAPPER); if (TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL.equals(fetchMode)) { entries.forEach(entry -> processArray(resultNode, entry)); } else { entries.forEach(entry -> processSingle(resultNode, entry)); } - + var copy = msg.getMetaData().copy(); for (String key : keys) { if (resultNode.has(key)) { - msg.getMetaData().putValue(key, resultNode.get(key).toString()); + copy.putValue(key, resultNode.get(key).toString()); } } + return copy; } private void processSingle(ObjectNode node, TsKvEntry entry) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index e368358299..8cccc51258 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.rule.RuleNodeState; @@ -54,18 +55,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_ACK; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_CLEAR; -import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; -import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; -import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED; -import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UNASSIGNED; -import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; - @Slf4j class DeviceState { @@ -147,24 +136,24 @@ class DeviceState { latestValues = fetchLatestValues(ctx, deviceId); } boolean stateChanged = false; - if (msg.getType().equals(POST_TELEMETRY_REQUEST.name())) { + if (msg.getType().equals(TbMsgType.POST_TELEMETRY_REQUEST.name())) { stateChanged = processTelemetry(ctx, msg); - } else if (msg.getType().equals(POST_ATTRIBUTES_REQUEST.name())) { + } else if (msg.getType().equals(TbMsgType.POST_ATTRIBUTES_REQUEST.name())) { stateChanged = processAttributesUpdateRequest(ctx, msg); - } else if (msg.getType().equals(ACTIVITY_EVENT.name()) || msg.getType().equals(INACTIVITY_EVENT.name())) { + } else if (msg.getType().equals(TbMsgType.ACTIVITY_EVENT.name()) || msg.getType().equals(TbMsgType.INACTIVITY_EVENT.name())) { stateChanged = processDeviceActivityEvent(ctx, msg); - } else if (msg.getType().equals(ATTRIBUTES_UPDATED.name())) { + } else if (msg.getType().equals(TbMsgType.ATTRIBUTES_UPDATED.name())) { stateChanged = processAttributesUpdateNotification(ctx, msg); - } else if (msg.getType().equals(ATTRIBUTES_DELETED.name())) { + } else if (msg.getType().equals(TbMsgType.ATTRIBUTES_DELETED.name())) { stateChanged = processAttributesDeleteNotification(ctx, msg); - } else if (msg.getType().equals(ALARM_CLEAR.name())) { + } else if (msg.getType().equals(TbMsgType.ALARM_CLEAR.name())) { stateChanged = processAlarmClearNotification(ctx, msg); - } else if (msg.getType().equals(ALARM_ACK.name())) { + } else if (msg.getType().equals(TbMsgType.ALARM_ACK.name())) { processAlarmAckNotification(ctx, msg); - } else if (msg.getType().equals(ALARM_DELETE.name())) { + } else if (msg.getType().equals(TbMsgType.ALARM_DELETE.name())) { processAlarmDeleteNotification(ctx, msg); } else { - if (msg.getType().equals(ENTITY_ASSIGNED.name()) || msg.getType().equals(ENTITY_UNASSIGNED.name())) { + if (msg.getType().equals(TbMsgType.ENTITY_ASSIGNED.name()) || msg.getType().equals(TbMsgType.ENTITY_UNASSIGNED.name())) { dynamicPredicateValueCtx.resetCustomer(); } ctx.tellSuccess(msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index 0abb15f279..0475728c4d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -170,7 +170,7 @@ public class TbDeviceProfileNode implements TbNode { } protected void scheduleAlarmHarvesting(TbContext ctx, TbMsg msg) { - TbMsg periodicCheck = TbMsg.newMsg(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG, ctx.getTenantId(), msg != null ? msg.getCustomerId() : null, TbMsgMetaData.EMPTY, "{}"); + TbMsg periodicCheck = TbMsg.newMsg(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG, ctx.getTenantId(), msg != null ? msg.getCustomerId() : null, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); ctx.tellSelf(periodicCheck, TimeUnit.MINUTES.toMillis(1)); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java index 7a9d95cad4..f36cc94540 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java @@ -122,7 +122,7 @@ public class TbCreateRelationNodeTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("name", "AssetName"); metaData.putValue("type", "AssetType"); - msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); + msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); EntityRelation relation = new EntityRelation(); when(ctx.getRelationService().findByToAndTypeAsync(any(), eq(msg.getOriginator()), eq(EntityRelation.CONTAINS_TYPE), eq(RelationTypeGroup.COMMON))) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java index 0c51462429..f44326a8d5 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java @@ -125,7 +125,7 @@ public class TbMsgPushToEdgeNodeTest { @Test public void testMiscEventsProcessedAsTimeseriesUpdated() { for (var event : MISC_EVENTS) { - testEvent(event, new TbMsgMetaData(), EdgeEventActionType.TIMESERIES_UPDATED, "data"); + testEvent(event, TbMsgMetaData.EMPTY, EdgeEventActionType.TIMESERIES_UPDATED, "data"); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 6ce33fd009..639c1a7b1a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -59,7 +59,7 @@ public class TbJsFilterNodeTest { @Test public void falseEvaluationDoNotSendMsg() throws TbNodeException { initWithScript(); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, new TbMsgMetaData(), TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, null, TbMsgMetaData.EMPTY, TbMsgDataType.JSON, TbMsg.EMPTY_JSON_OBJECT, ruleChainId, ruleNodeId); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(false)); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java index 64a239d57f..f4585d0f96 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingFilterNodeTest.java @@ -452,7 +452,7 @@ class TbGpsGeofencingFilterNodeTest { } private TbMsg getEmptyArrayTbMsg(EntityId entityId) { - return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, "[]"); + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java index 3e19c7a736..0a42349433 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java @@ -247,7 +247,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).put("b", arg2).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", arg1).put("b", arg2).toString()); node.onMsg(ctx, msg); @@ -270,7 +270,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", arg1).toString()); node.onMsg(ctx, msg); @@ -293,7 +293,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); node.onMsg(ctx, msg); @@ -316,7 +316,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString()); node.onMsg(ctx, msg); @@ -340,7 +340,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.TIME_SERIES, "b") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().toString()); Mockito.when(attributesService.find(tenantId, originator, DataConstants.SERVER_SCOPE, "a")) .thenReturn(Futures.immediateFuture(Optional.of(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("a", 2.0))))); @@ -368,7 +368,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); node.onMsg(ctx, msg); @@ -390,7 +390,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); node.onMsg(ctx, msg); @@ -412,7 +412,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); Mockito.when(telemetryService.saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble())) .thenReturn(Futures.immediateFuture(null)); @@ -438,7 +438,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) .thenReturn(Futures.immediateFuture(null)); @@ -463,7 +463,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) .thenReturn(Futures.immediateFuture(null)); @@ -494,7 +494,7 @@ public class TbMathNodeTest { new TbMathResult(TbMathArgumentType.MESSAGE_METADATA, "result", 3, false, false, null), tbMathArgument ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 10).toString()); node.onMsg(ctx, msg); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -514,7 +514,7 @@ public class TbMathNodeTest { new TbMathResult(TbMathArgumentType.TIME_SERIES, "result", 3, true, false, DataConstants.SERVER_SCOPE), new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 10).toString()); Throwable thrown = assertThrows(RuntimeException.class, () -> { node.onMsg(ctx, msg); }); @@ -528,7 +528,7 @@ public class TbMathNodeTest { new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") ); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, new TbMsgMetaData(), "[]"); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); Throwable thrown = assertThrows(RuntimeException.class, () -> { node.onMsg(ctx, msg); }); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java index 9dbae201ac..2f99fbb43c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java @@ -117,8 +117,7 @@ public class CalculateDeltaNodeTest { @Test public void givenInvalidMsgDataType_whenOnMsg_thenShouldTellNextOther() { // GIVEN - var msgData = "[]"; - var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN node.onMsg(ctxMock, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java index a889f91b77..2263b272b9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeTest.java @@ -241,7 +241,7 @@ public class TbGetAttributesNodeTest { public void givenFetchLatestTimeseriesToDataAndDataIsNotJsonObject_whenOnMsg_thenException() throws Exception { // GIVEN node = initNode(FetchTo.DATA, true, true); - var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index 3a8f36f5dc..b413d2c576 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -208,7 +208,7 @@ public class TbGetCustomerAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index 8b958bcc3c..1908578801 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -157,7 +157,7 @@ public class TbGetCustomerDetailsNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index 0057ec7c61..dfc80d1752 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -133,7 +133,7 @@ public class TbGetOriginatorFieldsNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java index 6b3a225eac..0965327df0 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java @@ -222,7 +222,7 @@ public class TbGetRelatedAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java index ffb053c93a..c7a23a512b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java @@ -188,7 +188,7 @@ public class TbGetTenantAttributeNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java index 2dda9ffaf0..23b1abea73 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java @@ -127,7 +127,7 @@ public class TbGetTenantDetailsNodeTest { public void givenMsgDataIsNotAnJsonObjectAndFetchToData_whenOnMsg_thenException() { // GIVEN node.fetchTo = FetchTo.DATA; - msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, "[]"); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); // WHEN var exception = assertThrows(IllegalArgumentException.class, () -> node.onMsg(ctxMock, msg)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java index 6bc4a59610..eef77a5304 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/DeviceStateTest.java @@ -107,7 +107,7 @@ public class DeviceStateTest { DeviceState deviceState = createDeviceState(deviceId, alarmConfig); TbMsg attributeUpdateMsg = TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, - deviceId, new TbMsgMetaData(), "{ \"enabled\": false }"); + deviceId, TbMsgMetaData.EMPTY, "{ \"enabled\": false }"); deviceState.process(ctx, attributeUpdateMsg); @@ -119,7 +119,7 @@ public class DeviceStateTest { reset(ctx); String deletedAttributes = "{ \"attributes\": [ \"other\" ] }"; - deviceState.process(ctx, TbMsg.newMsg(TbMsgType.ATTRIBUTES_DELETED, deviceId, new TbMsgMetaData(), deletedAttributes)); + deviceState.process(ctx, TbMsg.newMsg(TbMsgType.ATTRIBUTES_DELETED, deviceId, TbMsgMetaData.EMPTY, deletedAttributes)); verify(ctx, never()).enqueueForTellNext(any(), anyString()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java index 2ddc20d5ed..48aca3b573 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java @@ -145,7 +145,7 @@ public class TbHttpClientTest { var httpClient = new TbHttpClient(config, eventLoop); httpClient.setHttpClient(asyncRestTemplate); - var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, new DeviceId(EntityId.NULL_UUID), TbMsgMetaData.EMPTY, "{}"); + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, new DeviceId(EntityId.NULL_UUID), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); var successMsg = TbMsg.newMsg( TbMsgType.POST_TELEMETRY_REQUEST, msg.getOriginator(), msg.getMetaData(), msg.getData() diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java index 5d8901dcad..e0ab34e35b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java @@ -79,8 +79,7 @@ public class TbCopyKeysNodeTest { @Test void givenMsgFromMetadata_whenOnMsg_thenVerifyOutput() throws Exception { - String data = "{}"; - node.onMsg(ctx, getTbMsg(deviceId, data)); + node.onMsg(ctx, getTbMsg(deviceId, TbMsg.EMPTY_JSON_OBJECT)); ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); @@ -137,8 +136,7 @@ public class TbCopyKeysNodeTest { @Test void givenMsgDataNotJSONObject_whenOnMsg_thenTVerifyOutput() throws Exception { - String data = "[]"; - TbMsg msg = getTbMsg(deviceId, data); + TbMsg msg = getTbMsg(deviceId, TbMsg.EMPTY_JSON_ARRAY); node.onMsg(ctx, msg); ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java index 838f35e25e..6eee16e7d3 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java @@ -79,8 +79,7 @@ public class TbDeleteKeysNodeTest { @Test void givenMsgFromMetadata_whenOnMsg_thenVerifyOutput() throws Exception { - String data = "{}"; - node.onMsg(ctx, getTbMsg(deviceId, data)); + node.onMsg(ctx, getTbMsg(deviceId, TbMsg.EMPTY_JSON_OBJECT)); ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java index f226f2e166..c602aab5df 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java @@ -135,8 +135,7 @@ public class TbRenameKeysNodeTest { @Test void givenMsgDataNotJSONObject_whenOnMsg_thenVerifyOutput() throws Exception { - String data = "[]"; - TbMsg msg = getTbMsg(deviceId, data); + TbMsg msg = getTbMsg(deviceId, TbMsg.EMPTY_JSON_ARRAY); node.onMsg(ctx, msg); ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java index 5cdb0d305b..e7e2ae16b2 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNodeTest.java @@ -83,8 +83,7 @@ public class TbSplitArrayMsgNodeTest { @Test void givenZeroMsg_whenOnMsg_thenVerifyOutput() throws Exception { - String data = "[]"; - VerifyOutputMsg(data); + VerifyOutputMsg(TbMsg.EMPTY_JSON_ARRAY); } @Test From ffdb16766ce033a4002bad6a4675693178230178 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 6 Jul 2023 13:31:25 +0200 Subject: [PATCH 025/166] improvements --- .../queue/discovery/ZkDiscoveryService.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 17d046a4cb..24a7863b24 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -299,16 +299,16 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi case CHILD_ADDED: ScheduledFuture task = delayedTasks.remove(instance.getServiceId()); if (task != null) { - if (!task.cancel(false)) { - log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + if (task.cancel(false)) { + log.debug("[{}] Recalculate partitions ignored. Service was restarted in time [{}].", instance.getServiceId(), instance.getServiceTypesList()); - recalculatePartitions(); } else { - log.debug("[{}] Recalculate partitions ignored. Service restarted in time [{}]", + log.debug("[{}] Going to recalculate partitions. Service was not restarted in time [{}]!", instance.getServiceId(), instance.getServiceTypesList()); + recalculatePartitions(); } } else { - log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + log.debug("[{}] Going to recalculate partitions due to adding new node [{}].", instance.getServiceId(), instance.getServiceTypesList()); recalculatePartitions(); } @@ -317,8 +317,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi ScheduledFuture future = zkExecutorService.schedule(() -> { log.debug("[{}] Going to recalculate partitions due to removed node [{}]", instance.getServiceId(), instance.getServiceTypesList()); - delayedTasks.remove(instance.getServiceId()); - recalculatePartitions(); + ScheduledFuture removedTask = delayedTasks.remove(instance.getServiceId()); + if (removedTask != null) { + recalculatePartitions(); + } }, recalculateDelay, TimeUnit.MILLISECONDS); delayedTasks.put(instance.getServiceId(), future); break; @@ -332,6 +334,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi * Synchronized to ensure that other servers info is up to date * */ synchronized void recalculatePartitions() { + delayedTasks.clear(); partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers()); } From 14216a48827b9f59d836b7362a8d296899c1b0a2 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 6 Jul 2023 15:03:32 +0300 Subject: [PATCH 026/166] fixed security transport cases --- .../src/main/resources/thingsboard.yml | 12 ++ .../server/dao/device/DeviceService.java | 1 + .../dao/device/DeviceConnectivityInfo.java | 5 +- .../server/dao/device/DeviceServiceImpl.java | 126 ++++++++++++------ 4 files changed, 97 insertions(+), 47 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 2f58590bf6..3615e25825 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -981,16 +981,28 @@ transport: # Device connectivity properties to publish telemetry device: connectivity: + http: + enabled: "${DEVICE_CONNECTIVITY_HTTP_ENABLED:true}" + host: "${DEVICE_CONNECTIVITY_HTTP_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_HTTP_PORT:8080}" + https: + enabled: "${DEVICE_CONNECTIVITY_HTTPS_ENABLED:false}" + host: "${DEVICE_CONNECTIVITY_HTTPS_HOST:localhost}" + port: "${DEVICE_CONNECTIVITY_HTTPS_PORT:443}" mqtt: + enabled: "${DEVICE_CONNECTIVITY_MQTT_ENABLED:true}" host: "${DEVICE_CONNECTIVITY_MQTT_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_MQTT_PORT:1883}" mqtts: + enabled: "${DEVICE_CONNECTIVITY_MQTT_ENABLED:false}" host: "${DEVICE_CONNECTIVITY_MQTTS_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_MQTTS_PORT:8883}" coap: + enabled: "${DEVICE_CONNECTIVITY_MQTT_ENABLED:true}" host: "${DEVICE_CONNECTIVITY_COAP_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_COAP_PORT:5683}" coaps: + enabled: "${DEVICE_CONNECTIVITY_MQTT_ENABLED:false}" host: "${DEVICE_CONNECTIVITY_COAPS_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_COAPS_PORT:5684}" diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index 72c6a8852c..a029f27309 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.device; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java index 7b477bfc42..f570919290 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java @@ -16,11 +16,10 @@ package org.thingsboard.server.dao.device; import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - @Data public class DeviceConnectivityInfo { + private Boolean enabled; private String host; - private Integer port; + private String port; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index fe5ac33e73..1a881d82bd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -80,6 +80,8 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -104,8 +106,15 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDevicePublishTelemetryCommands(String baseUrl, Device device) { + public Map findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException { DeviceId deviceId = device.getId(); log.trace("Executing findDevicePublishTelemetryCommands [{}]", deviceId); validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); + String defaultHostname = new URI(baseUrl).getHost(); DeviceCredentials creds = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); - DeviceCredentialsType credentialsType = creds.getCredentialsType(); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); DeviceTransportType transportType = deviceProfile.getTransportType(); Map commands = new HashMap<>(); - switch (transportType) { case DEFAULT: - Optional.ofNullable(getHttpPublishCommand(baseUrl, creds)).ifPresent(v -> commands.put("http", v)); - Optional.ofNullable(getMqttPublishCommand(creds)).ifPresent(v -> commands.put("mqtt", v)); - Optional.ofNullable(getMqttsPublishCommand(creds)).ifPresent(v -> commands.put("mqtts", v)); - Optional.ofNullable(getCoapPublishCommand(creds)).ifPresent(v -> commands.put("coap", v)); - Optional.ofNullable(getCoapsPublishCommand(creds)).ifPresent(v -> commands.put("coaps", v)); + Optional.ofNullable(getHttpPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(HTTP_PROTOCOL, v)); + Optional.ofNullable(getHttpsPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(HTTPS_PROTOCOL, v)); + Optional.ofNullable(getMqttPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(MQTT_PROTOCOL, v)); + Optional.ofNullable(getMqttsPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(MQTTS_PROTOCOL, v)); + Optional.ofNullable(getCoapPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(COAP_PROTOCOL, v)); + Optional.ofNullable(getCoapsPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(COAPS_PROTOCOL, v)); break; case MQTT: MqttDeviceProfileTransportConfiguration transportConfiguration = @@ -164,19 +172,12 @@ public class DeviceServiceImpl extends AbstractCachedEntityService commands.put("mqtt", v)); - Optional.ofNullable(getMqttsPublishCommand(topicName, creds, payload)).ifPresent(v -> commands.put("mqtts", v)); + Optional.ofNullable(getMqttPublishCommand(defaultHostname, topicName, creds, payload)).ifPresent(v -> commands.put(MQTT_PROTOCOL, v)); + Optional.ofNullable(getMqttsPublishCommand(defaultHostname, topicName, creds, payload)).ifPresent(v -> commands.put(MQTTS_PROTOCOL, v)); break; case COAP: - CoapDeviceProfileTransportConfiguration coapTransportConfiguration = - (CoapDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); - CoapDeviceTypeConfiguration coapConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); - if (coapConfiguration instanceof DefaultCoapDeviceTypeConfiguration) { - Optional.ofNullable(getCoapPublishCommand(creds)).ifPresent(v -> commands.put("coap", v)); - Optional.ofNullable(getCoapsPublishCommand(creds)).ifPresent(v -> commands.put("coaps", v)); - } else if (coapConfiguration instanceof EfentoCoapDeviceTypeConfiguration) { - commands.put("coap for efento", "Not supported"); - } + Optional.ofNullable(getCoapPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(COAP_PROTOCOL, v)); + Optional.ofNullable(getCoapsPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(COAPS_PROTOCOL, v)); break; default: commands.put(transportType.name(), NOT_SUPPORTED); @@ -743,24 +744,45 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Thu, 6 Jul 2023 15:06:07 +0300 Subject: [PATCH 027/166] minor refactoring --- .../server/dao/device/DeviceServiceImpl.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 1a881d82bd..a0a8b9dbcd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -48,10 +48,6 @@ import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; -import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; -import org.thingsboard.server.common.data.device.profile.EfentoCoapDeviceTypeConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; @@ -745,7 +741,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Thu, 6 Jul 2023 15:18:16 +0300 Subject: [PATCH 028/166] fixed descriptions for enrichment rule nodes --- .../rule/engine/filter/TbOriginatorTypeSwitchNode.java | 2 +- .../thingsboard/rule/engine/metadata/CalculateDeltaNode.java | 3 ++- .../rule/engine/metadata/TbFetchDeviceCredentialsNode.java | 4 +++- .../thingsboard/rule/engine/metadata/TbGetAttributesNode.java | 3 ++- .../rule/engine/metadata/TbGetCustomerAttributeNode.java | 3 ++- .../rule/engine/metadata/TbGetCustomerDetailsNode.java | 3 ++- .../thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java | 4 +++- .../rule/engine/metadata/TbGetOriginatorFieldsNode.java | 3 ++- .../rule/engine/metadata/TbGetRelatedAttributeNode.java | 3 ++- .../thingsboard/rule/engine/metadata/TbGetTelemetryNode.java | 3 ++- .../rule/engine/metadata/TbGetTenantAttributeNode.java | 3 ++- .../rule/engine/metadata/TbGetTenantDetailsNode.java | 3 ++- 12 files changed, 25 insertions(+), 12 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java index 9e14c6a7ab..17b7d575f5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; relationTypes = {}, // should always be empty. We add the relation types for this node in AnnotationComponentDiscoveryService. nodeDescription = "Route incoming messages by Message Originator Type", nodeDetails = "Routes messages to chain according to the entity type ('Device', 'Asset', etc.).

" + - "Output connections: Message originator type or Failure", + "Output connections: Message originator type or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbOriginatorTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index 25b9205602..3e4e6eb93f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -50,7 +50,8 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; configClazz = CalculateDeltaNodeConfiguration.class, nodeDescription = "Calculates delta and amount of time passed between previous timeseries key reading " + "and current value for this key from the incoming message", - nodeDetails = "Useful for metering use cases, when you need to calculate consumption based on pulse counter reading.", + nodeDetails = "Useful for metering use cases, when you need to calculate consumption based on pulse counter reading.

" + + "Output connections: Success, Other or Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeCalculateDeltaConfig") public class CalculateDeltaNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java index e23da1e364..d8934535af 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java @@ -41,7 +41,9 @@ import java.util.concurrent.ExecutionException; nodeDescription = "Adds device credentials to the message or message metadata", nodeDetails = "if message originator type is Device and device credentials was successfully fetched, " + "rule node enriches message or message metadata with credentialsType and credentials properties. " + - "Useful when you need to fetch device credentials and use them for further message processing. For example, use device credentials to interact with external systems.", + "Useful when you need to fetch device credentials and use them for further message processing. " + + "For example, use device credentials to interact with external systems.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeFetchDeviceCredentialsConfig") public class TbFetchDeviceCredentialsNode extends TbAbstractNodeWithFetchTo { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 975da84510..edc7bdc44e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -40,7 +40,8 @@ import org.thingsboard.server.common.msg.TbMsg; nodeDescription = "Adds attributes and/or latest timeseries data for the message originator to the message or message metadata", nodeDetails = "Useful when you need to retrieve some attributes or the latest telemetry readings from the message originator " + "that are not included in the incoming message to use them for further message processing. " + - "For example to filter messages based on the threshold value stored in the attributes.", + "For example to filter messages based on the threshold value stored in the attributes.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeOriginatorAttributesConfig") public class TbGetAttributesNode extends TbAbstractGetAttributesNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index f5b50f259c..088aa596fc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -39,7 +39,8 @@ import org.thingsboard.server.common.data.util.TbPair; nodeDescription = "Adds message originator customer attributes or latest telemetry into message or message metadata", nodeDetails = "Useful in multi-customer solutions where each customer has a different configuration or threshold set " + "that is stored as customer attributes or telemetry data and used for dynamic message filtering, transformation, " + - "or actions such as alarm creation if the threshold is exceeded.", + "or actions such as alarm creation if the threshold is exceeded.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeCustomerAttributesConfig") public class TbGetCustomerAttributeNode extends TbAbstractGetEntityDataNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java index 3be5ac15c3..377e9eb0da 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java @@ -47,7 +47,8 @@ import java.util.NoSuchElementException; version = 1, nodeDescription = "Adds message originator customer details into message or message metadata", nodeDetails = "Useful in multi-customer solutions where we need dynamically use customer contact information " + - "such as email, phone, address, etc., for notifications via email, SMS, and other notification providers.", + "such as email, phone, address, etc., for notifications via email, SMS, and other notification providers.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeEntityDetailsConfig") public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java index 67e544fcfa..eb82d26007 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java @@ -38,7 +38,9 @@ import org.thingsboard.server.common.msg.TbMsg; nodeDescription = "Add originators related device attributes and/or latest telemetry values into message or message metadata", nodeDetails = "Related device lookup based on the configured relation query. " + "If multiple related devices are found, only first device is used for message enrichment, other entities are discarded. " + - "Useful when you need to retrieve attributes and/or latest telemetry values from device that has a relation to the message originator and use them for further message processing.", + "Useful when you need to retrieve attributes and/or latest telemetry values from device that has a relation " + + "to the message originator and use them for further message processing.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeDeviceAttributesConfig") public class TbGetDeviceAttrNode extends TbAbstractGetAttributesNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java index 34153360a3..30c3e10cde 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNode.java @@ -40,7 +40,8 @@ import java.util.concurrent.ExecutionException; version = 1, nodeDescription = "Adds message originator fields values into message or message metadata", nodeDetails = "Fetches fields values specified in the mapping. If specified field is not part of originator fields it will be ignored. " + - "Useful when you need to retrieve originator fields and use them for further message processing.", + "Useful when you need to retrieve originator fields and use them for further message processing.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeOriginatorFieldsConfig") public class TbGetOriginatorFieldsNode extends TbAbstractGetMappedDataNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index 249caa87ac..50e75c8cc0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -40,7 +40,8 @@ import java.util.Arrays; nodeDescription = "Adds originators related entity attributes or latest telemetry or fields into message or message metadata", nodeDetails = "Related entity lookup based on the configured relation query. " + "If multiple related entities are found, only first entity is used for message enrichment, other entities are discarded. " + - "Useful when you need to retrieve data from an entity that has a relation to the message originator and use them for further message processing.", + "Useful when you need to retrieve data from an entity that has a relation to the message originator and use them for further message processing.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeRelatedAttributesConfig") public class TbGetRelatedAttributeNode extends TbAbstractGetEntityDataNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java index a7ddf2a76b..08fa2a6246 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java @@ -54,7 +54,8 @@ import java.util.stream.Collectors; nodeDescription = "Adds message originator telemetry for selected time range into message metadata", nodeDetails = "Useful when you need to get telemetry data set from the message originator for a specific time range " + "instead of fetching just the latest telemetry or if you need to get the closest telemetry to the fetch interval start or end. " + - "Also, this node can be used for telemetry aggregation within configured fetch interval.", + "Also, this node can be used for telemetry aggregation within configured fetch interval.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeGetTelemetryFromDatabase") public class TbGetTelemetryNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java index cbbf00ce92..bcd54cd829 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -37,7 +37,8 @@ import org.thingsboard.server.common.data.util.TbPair; version = 1, nodeDescription = "Adds message originator tenant attributes or latest telemetry into message or message metadata", nodeDetails = "Useful when you need to retrieve some common configuration or threshold set " + - "that is stored as tenant attributes or telemetry data and use it for further message processing.", + "that is stored as tenant attributes or telemetry data and use it for further message processing.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeTenantAttributesConfig") public class TbGetTenantAttributeNode extends TbAbstractGetEntityDataNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java index 8e293aea37..2a5039f849 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNode.java @@ -36,7 +36,8 @@ import org.thingsboard.server.common.msg.TbMsg; version = 1, nodeDescription = "Adds message originator tenant details into message or message metadata", nodeDetails = "Useful when we need to retrieve contact information from your tenant " + - "such as email, phone, address, etc., for notifications via email, SMS, and other notification providers.", + "such as email, phone, address, etc., for notifications via email, SMS, and other notification providers.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeEntityDetailsConfig") public class TbGetTenantDetailsNode extends TbAbstractGetEntityDetailsNode { From bb71ae39c3ed381241e2606202f9e27dd92d9de1 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 6 Jul 2023 17:24:13 +0300 Subject: [PATCH 029/166] added tests --- .../controller/DeviceControllerTest.java | 175 +++++++++++++++--- 1 file changed, 150 insertions(+), 25 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 96aa5638db..477500508f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -34,6 +34,7 @@ import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; @@ -51,14 +52,16 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceCredentialsId; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -91,12 +94,19 @@ import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; +@TestPropertySource(properties = { + "device.connectivity.https.enabled=true", + "device.connectivity.mqtts.enabled=true", + "device.connectivity.coaps.enabled=true", +}) @ContextConfiguration(classes = {DeviceControllerTest.Config.class}) @DaoSqlTest public class DeviceControllerTest extends AbstractControllerTest { static final TypeReference> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() { }; + private static final String DEVICE_TELEMETRY_TOPIC = "v1/devices/customTopic"; + ListeningExecutorService executor; List> futures; @@ -104,6 +114,8 @@ public class DeviceControllerTest extends AbstractControllerTest { private Tenant savedTenant; private User tenantAdmin; + private DeviceProfileId mqttDeviceProfileId; + private DeviceProfileId coapDeviceProfileId; @SpyBean private GatewayNotificationsService gatewayNotificationsService; @@ -138,6 +150,34 @@ public class DeviceControllerTest extends AbstractControllerTest { tenantAdmin.setLastName("Downs"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + DeviceProfile mqttProfile = new DeviceProfile(); + mqttProfile.setName("Mqtt device profile"); + mqttProfile.setType(DeviceProfileType.DEFAULT); + mqttProfile.setTransportType(DeviceTransportType.MQTT); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); + MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration(); + transportConfiguration.setDeviceTelemetryTopic(DEVICE_TELEMETRY_TOPIC); + deviceProfileData.setTransportConfiguration(transportConfiguration); + mqttProfile.setProfileData(deviceProfileData); + mqttProfile.setDefault(false); + mqttProfile.setDefaultRuleChainId(null); + + mqttDeviceProfileId = doPost("/api/deviceProfile", mqttProfile, DeviceProfile.class).getId(); + + DeviceProfile coapProfile = new DeviceProfile(); + coapProfile.setName("Coap device profile"); + coapProfile.setType(DeviceProfileType.DEFAULT); + coapProfile.setTransportType(DeviceTransportType.COAP); + DeviceProfileData deviceProfileData2 = new DeviceProfileData(); + deviceProfileData2.setConfiguration(new DefaultDeviceProfileConfiguration()); + deviceProfileData2.setTransportConfiguration(new CoapDeviceProfileTransportConfiguration()); + coapProfile.setProfileData(deviceProfileData); + coapProfile.setDefault(false); + coapProfile.setDefaultRuleChainId(null); + + coapDeviceProfileId = doPost("/api/deviceProfile", coapProfile, DeviceProfile.class).getId(); } @After @@ -655,51 +695,136 @@ public class DeviceControllerTest extends AbstractControllerTest { device.setName("My device"); device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); - List commands = + Map commands = doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); DeviceCredentials credentials = doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - assertThat(commands).hasSize(3); - assertThat(commands).containsExactly(String.format("mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", - credentials.getCredentialsId()), - String.format("curl -v -X POST http://localhost:80/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", - credentials.getCredentialsId()), - String.format("echo -n \"{temperature:25}\" | coap-client -m post coap://localhost:5683/api/v1/%s/telemetry -f-", - credentials.getCredentialsId())); + assertThat(commands).hasSize(6); + assertThat(commands.get("http")).isEqualTo(String.format("curl -v -X POST http://localhost:8080/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(commands.get("https")).isEqualTo(String.format("curl -v -X POST https://localhost:443/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(commands.get("mqtt")).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub --cafile tb-server-chain.pem -d -q 1 -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(commands.get("coap")).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(commands.get("coaps")).isEqualTo(String.format("coap-client-openssl -v 9 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); } @Test - public void testFetchPublishTelemetryCommandsForMqttDevice() throws Exception { - DeviceProfile mqttProfile = new DeviceProfile(); - mqttProfile.setName("Mqtt device profile"); - mqttProfile.setType(DeviceProfileType.DEFAULT); - mqttProfile.setTransportType(DeviceTransportType.MQTT); + public void testFetchPublishTelemetryCommandsForMqttDeviceWithAccessToken() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(mqttDeviceProfileId); - DeviceProfileData deviceProfileData = new DeviceProfileData(); - deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); - deviceProfileData.setTransportConfiguration(new MqttDeviceProfileTransportConfiguration()); + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - mqttProfile.setProfileData(deviceProfileData); - mqttProfile.setDefault(false); - mqttProfile.setDefaultRuleChainId(null); + Map commands = + doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); + assertThat(commands).hasSize(2); + assertThat(commands.get("mqtt")).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub --cafile tb-server-chain.pem -d -q 1 -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + } - mqttProfile = doPost("/api/deviceProfile", mqttProfile, DeviceProfile.class); + @Test + public void testFetchPublishTelemetryCommandsForDeviceWithMqttBasicCreds() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(mqttDeviceProfileId); + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + credentials.setCredentialsId(null); + credentials.setCredentialsType(DeviceCredentialsType.MQTT_BASIC); + BasicMqttCredentials basicMqttCredentials = new BasicMqttCredentials(); + String clientId = "testClientId"; + String userName = "testUsername"; + String password = "testPassword"; + basicMqttCredentials.setClientId(clientId); + basicMqttCredentials.setUserName(userName); + basicMqttCredentials.setPassword(password); + credentials.setCredentialsValue(JacksonUtil.toString(basicMqttCredentials)); + doPost("/api/device/credentials", credentials) + .andExpect(status().isOk()); + + Map commands = + doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); + assertThat(commands).hasSize(2); + assertThat(commands.get("mqtt")).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub --cafile tb-server-chain.pem -d -q 1 -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + } + + @Test + public void testFetchPublishTelemetryCommandsForDeviceWithX509Creds() throws Exception { Device device = new Device(); device.setName("My device"); - device.setDeviceProfileId(mqttProfile.getId()); + device.setDeviceProfileId(mqttDeviceProfileId); Device savedDevice = doPost("/api/device", device, Device.class); DeviceCredentials credentials = doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + credentials.setCredentialsId(null); + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + credentials.setCredentialsValue("testValue"); + doPost("/api/device/credentials", credentials) + .andExpect(status().isOk()); + + Map commands = + doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); + assertThat(commands).hasSize(1); + assertThat(commands.get("mqtts")).isEqualTo("Not supported"); + } + + @Test + public void testFetchPublishTelemetryCommandsForСoapDevice() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(coapDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + Map commands = + doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); + assertThat(commands).hasSize(2); + assertThat(commands.get("coap")).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(commands.get("coaps")).isEqualTo(String.format("coap-client-openssl -v 9 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + } + + @Test + public void testFetchPublishTelemetryCommandsForСoapDeviceWithX509Creds() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(coapDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + credentials.setCredentialsId(null); + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + credentials.setCredentialsValue("testValue"); + doPost("/api/device/credentials", credentials) + .andExpect(status().isOk()); - List commands = + Map commands = doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); assertThat(commands).hasSize(1); - assertThat(commands.get(0)).isEqualTo("mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -u " - + credentials.getCredentialsId() + " -m \"{temperature:25}\""); + assertThat(commands.get("coaps")).isEqualTo("Not supported"); } @Test From 2eeb3a1639e244bdc249df87269fe1ea05b24555 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 7 Jul 2023 10:59:52 +0300 Subject: [PATCH 030/166] refactoring --- .../controller/DeviceControllerTest.java | 10 +- .../server/dao/device/DeviceServiceImpl.java | 101 ++++++------------ 2 files changed, 39 insertions(+), 72 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 477500508f..47de391bf3 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -708,7 +708,7 @@ public class DeviceControllerTest extends AbstractControllerTest { credentials.getCredentialsId())); assertThat(commands.get("mqtt")).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub --cafile tb-server-chain.pem -d -q 1 -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); assertThat(commands.get("coap")).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); @@ -731,7 +731,7 @@ public class DeviceControllerTest extends AbstractControllerTest { assertThat(commands).hasSize(2); assertThat(commands.get("mqtt")).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub --cafile tb-server-chain.pem -d -q 1 -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"", + assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); } @@ -762,7 +762,7 @@ public class DeviceControllerTest extends AbstractControllerTest { assertThat(commands).hasSize(2); assertThat(commands.get("mqtt")).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub --cafile tb-server-chain.pem -d -q 1 -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); } @@ -784,7 +784,7 @@ public class DeviceControllerTest extends AbstractControllerTest { Map commands = doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); assertThat(commands).hasSize(1); - assertThat(commands.get("mqtts")).isEqualTo("Not supported"); + assertThat(commands.get("mqtts")).isEqualTo("Not provided"); } @Test @@ -824,7 +824,7 @@ public class DeviceControllerTest extends AbstractControllerTest { Map commands = doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); assertThat(commands).hasSize(1); - assertThat(commands.get("coaps")).isEqualTo("Not supported"); + assertThat(commands.get("coaps")).isEqualTo("Not provided"); } @Test diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index a0a8b9dbcd..1f14c32887 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -109,7 +109,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService commands.put(COAPS_PROTOCOL, v)); break; default: - commands.put(transportType.name(), NOT_SUPPORTED); + commands.put(transportType.name(), NOT_PROVIDED); } return commands; } @@ -765,48 +765,11 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Fri, 7 Jul 2023 14:07:02 +0300 Subject: [PATCH 031/166] added test for case when delta is negative and tell failure if delta is negative set to false --- .../metadata/CalculateDeltaNodeTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java index 2f99fbb43c..0fd8793e82 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeTest.java @@ -353,7 +353,33 @@ public class CalculateDeltaNodeTest { assertEquals(msg, actualMsgCaptor.getValue()); assertInstanceOf(IllegalArgumentException.class, actualException); assertEquals(expectedExceptionMsg, actualException.getMessage()); + } + + @Test + public void givenNegativeDeltaAndTellFailureIfNegativeDeltaFalse_whenOnMsg_thenShouldTellSuccess() throws TbNodeException { + // GIVEN + config.setTellFailureIfDeltaIsNegative(false); + nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + node.init(ctxMock, nodeConfiguration); + mockFindLatest(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("pulseCounter", 200L))); + + var msgData = "{\"pulseCounter\":\"123\"}"; + var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DUMMY_DEVICE_ORIGINATOR, TbMsgMetaData.EMPTY, msgData); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var actualMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + + verify(ctxMock, times(1)).tellSuccess(actualMsgCaptor.capture()); + verify(ctxMock, never()).tellFailure(any(), any()); + verify(ctxMock, never()).tellNext(any(), anyString()); + verify(ctxMock, never()).tellNext(any(), anySet()); + + String expectedMsgData = "{\"pulseCounter\":\"123\",\"delta\":-77}"; + assertEquals(expectedMsgData, actualMsgCaptor.getValue().getData()); } @Test From 21329bf74edb2a4e0aab0f84f1a20eef142dfd13 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 7 Jul 2023 14:14:06 +0300 Subject: [PATCH 032/166] refactoring --- .../src/main/resources/thingsboard.yml | 6 +- .../controller/DeviceControllerTest.java | 35 ++-- .../server/dao/device/DeviceServiceImpl.java | 158 +++++------------- .../dao/util/DeviceConnectivityUtil.java | 77 +++++++++ 4 files changed, 141 insertions(+), 135 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 3615e25825..284f95667b 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -994,15 +994,15 @@ device: host: "${DEVICE_CONNECTIVITY_MQTT_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_MQTT_PORT:1883}" mqtts: - enabled: "${DEVICE_CONNECTIVITY_MQTT_ENABLED:false}" + enabled: "${DEVICE_CONNECTIVITY_MQTTS_ENABLED:false}" host: "${DEVICE_CONNECTIVITY_MQTTS_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_MQTTS_PORT:8883}" coap: - enabled: "${DEVICE_CONNECTIVITY_MQTT_ENABLED:true}" + enabled: "${DEVICE_CONNECTIVITY_COAP_ENABLED:true}" host: "${DEVICE_CONNECTIVITY_COAP_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_COAP_PORT:5683}" coaps: - enabled: "${DEVICE_CONNECTIVITY_MQTT_ENABLED:false}" + enabled: "${DEVICE_CONNECTIVITY_COAPS_ENABLED:false}" host: "${DEVICE_CONNECTIVITY_COAPS_HOST:localhost}" port: "${DEVICE_CONNECTIVITY_COAPS_PORT:5684}" diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 47de391bf3..fea1784d30 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -93,6 +93,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAP; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; @TestPropertySource(properties = { "device.connectivity.https.enabled=true", @@ -106,6 +112,7 @@ public class DeviceControllerTest extends AbstractControllerTest { }; private static final String DEVICE_TELEMETRY_TOPIC = "v1/devices/customTopic"; + private static final String CHECK_DOCUMENTATION = "Check documentation"; ListeningExecutorService executor; @@ -702,17 +709,17 @@ public class DeviceControllerTest extends AbstractControllerTest { doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); assertThat(commands).hasSize(6); - assertThat(commands.get("http")).isEqualTo(String.format("curl -v -X POST http://localhost:8080/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", + assertThat(commands.get(HTTP)).isEqualTo(String.format("curl -v -X POST http://localhost:8080/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(commands.get("https")).isEqualTo(String.format("curl -v -X POST https://localhost:443/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", + assertThat(commands.get(HTTPS)).isEqualTo(String.format("curl -v -X POST https://localhost:443/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(commands.get("mqtt")).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + assertThat(commands.get(MQTT)).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + assertThat(commands.get(MQTTS)).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(commands.get("coap")).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + assertThat(commands.get(COAP)).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(commands.get("coaps")).isEqualTo(String.format("coap-client-openssl -v 9 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -v 9 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } @@ -729,9 +736,9 @@ public class DeviceControllerTest extends AbstractControllerTest { Map commands = doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); assertThat(commands).hasSize(2); - assertThat(commands.get("mqtt")).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -u %s -m \"{temperature:25}\"", + assertThat(commands.get(MQTT)).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"", + assertThat(commands.get(MQTTS)).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); } @@ -760,9 +767,9 @@ public class DeviceControllerTest extends AbstractControllerTest { Map commands = doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); assertThat(commands).hasSize(2); - assertThat(commands.get("mqtt")).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + assertThat(commands.get(MQTT)).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - assertThat(commands.get("mqtts")).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + assertThat(commands.get(MQTTS)).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); } @@ -784,7 +791,7 @@ public class DeviceControllerTest extends AbstractControllerTest { Map commands = doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); assertThat(commands).hasSize(1); - assertThat(commands.get("mqtts")).isEqualTo("Not provided"); + assertThat(commands.get(MQTTS)).isEqualTo(CHECK_DOCUMENTATION); } @Test @@ -800,9 +807,9 @@ public class DeviceControllerTest extends AbstractControllerTest { Map commands = doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); assertThat(commands).hasSize(2); - assertThat(commands.get("coap")).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + assertThat(commands.get(COAP)).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(commands.get("coaps")).isEqualTo(String.format("coap-client-openssl -v 9 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -v 9 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } @@ -824,7 +831,7 @@ public class DeviceControllerTest extends AbstractControllerTest { Map commands = doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); assertThat(commands).hasSize(1); - assertThat(commands.get("coaps")).isEqualTo("Not provided"); + assertThat(commands.get(COAPS)).isEqualTo(CHECK_DOCUMENTATION); } @Test diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 1f14c32887..9d0f9c38ac 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -91,6 +91,17 @@ import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateIds; import static org.thingsboard.server.dao.service.Validator.validatePageLink; import static org.thingsboard.server.dao.service.Validator.validateString; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAP; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.JSON_EXAMPLE_PAYLOAD; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.CHECK_DOCUMENTATION; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCoapClientCommand; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCurlCommand; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getMosquittoPublishCommand; @Service("DeviceDaoService") @Slf4j @@ -102,14 +113,6 @@ public class DeviceServiceImpl extends AbstractCachedEntityService commands = new HashMap<>(); switch (transportType) { case DEFAULT: - Optional.ofNullable(getHttpPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(HTTP_PROTOCOL, v)); - Optional.ofNullable(getHttpsPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(HTTPS_PROTOCOL, v)); - Optional.ofNullable(getMqttPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(MQTT_PROTOCOL, v)); - Optional.ofNullable(getMqttsPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(MQTTS_PROTOCOL, v)); - Optional.ofNullable(getCoapPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(COAP_PROTOCOL, v)); - Optional.ofNullable(getCoapsPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(COAPS_PROTOCOL, v)); + Optional.ofNullable(getHttpPublishCommand(HTTP, defaultHostname, creds)).ifPresent(v -> commands.put(HTTP, v)); + Optional.ofNullable(getHttpPublishCommand(HTTPS, defaultHostname, creds)).ifPresent(v -> commands.put(HTTPS, v)); + Optional.ofNullable(getMqttPublishCommand(MQTT, defaultHostname, creds)).ifPresent(v -> commands.put(MQTT, v)); + Optional.ofNullable(getMqttPublishCommand(MQTTS, defaultHostname, creds)).ifPresent(v -> commands.put(MQTTS, v)); + Optional.ofNullable(getCoapPublishCommand(COAP, defaultHostname, creds)).ifPresent(v -> commands.put(COAP, v)); + Optional.ofNullable(getCoapPublishCommand(COAPS, defaultHostname, creds)).ifPresent(v -> commands.put(COAPS, v)); break; case MQTT: MqttDeviceProfileTransportConfiguration transportConfiguration = (MqttDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); String topicName = transportConfiguration.getDeviceTelemetryTopic(); TransportPayloadType payloadType = transportConfiguration.getTransportPayloadTypeConfiguration().getTransportPayloadType(); - String payload = (payloadType == TransportPayloadType.PROTOBUF) ? " -f protobufFileName" : " -m " + PAYLOAD; + String payload = (payloadType == TransportPayloadType.PROTOBUF) ? " -f protobufFileName" : " -m " + JSON_EXAMPLE_PAYLOAD; - Optional.ofNullable(getMqttPublishCommand(defaultHostname, topicName, creds, payload)).ifPresent(v -> commands.put(MQTT_PROTOCOL, v)); - Optional.ofNullable(getMqttsPublishCommand(defaultHostname, topicName, creds, payload)).ifPresent(v -> commands.put(MQTTS_PROTOCOL, v)); + Optional.ofNullable(getMqttPublishCommand(MQTT, defaultHostname, topicName, creds, payload)).ifPresent(v -> commands.put(MQTT, v)); + Optional.ofNullable(getMqttPublishCommand(MQTTS, defaultHostname, topicName, creds, payload)).ifPresent(v -> commands.put(MQTTS, v)); break; case COAP: - Optional.ofNullable(getCoapPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(COAP_PROTOCOL, v)); - Optional.ofNullable(getCoapsPublishCommand(defaultHostname, creds)).ifPresent(v -> commands.put(COAPS_PROTOCOL, v)); + Optional.ofNullable(getCoapPublishCommand(COAP, defaultHostname, creds)).ifPresent(v -> commands.put(COAP, v)); + Optional.ofNullable(getCoapPublishCommand(COAPS, defaultHostname, creds)).ifPresent(v -> commands.put(COAPS, v)); break; default: - commands.put(transportType.name(), NOT_PROVIDED); + commands.put(transportType.name(), CHECK_DOCUMENTATION); } return commands; } @@ -740,119 +743,38 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Mon, 10 Jul 2023 12:14:55 +0300 Subject: [PATCH 033/166] added swagger response body example --- .../server/controller/DeviceController.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 260c1c91e0..e080574d36 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -21,8 +21,11 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -175,6 +178,15 @@ public class DeviceController extends BaseController { "If the user has the authority of 'Tenant Administrator', the server checks that the device is owned by the same tenant. " + "If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK", + examples = @io.swagger.annotations.Example( + value = { + @io.swagger.annotations.ExampleProperty( + mediaType="application/json", + value="{\"http\":\"curl -v -X POST http://localhost:8080/api/v1/0ySs4FTOn5WU15XLmal8/telemetry --header Content-Type:application/json --data {temperature:25}\"," + + "\"mqtt\":\"mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -i myClient1 -u myUsername1 -P myPassword -m {temperature:25}\"," + + "\"coap\":\"coap-client -m POST coap://localhost:5683/api/v1/0ySs4FTOn5WU15XLmal8/telemetry -t json -e {temperature:25}\"}")}))}) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device/{deviceId}/commands", method = RequestMethod.GET) @ResponseBody From f7b60e1c0e1b7ef27a42bcd80b54d942c566b33b Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 11 Jul 2023 10:40:04 +0300 Subject: [PATCH 034/166] UI: Redesign user menu: move profile and security menu item to account item --- ui-ngx/src/app/core/auth/auth.service.ts | 2 +- ui-ngx/src/app/core/services/menu.service.ts | 84 +++++++++++++++++++ ui-ngx/src/app/modules/home/home.component.ts | 9 +- .../modules/home/menu/side-menu.component.ts | 12 ++- .../pages/account/account-routing.module.ts | 54 ++++++++++++ .../home/pages/account/account.module.ts | 28 +++++++ .../modules/home/pages/home-pages.module.ts | 4 +- .../pages/profile/profile-routing.module.ts | 9 +- .../pages/security/security-routing.module.ts | 9 +- .../components/user-menu.component.html | 8 +- .../shared/components/user-menu.component.ts | 8 +- .../assets/locale/locale.constant-en_US.json | 4 + 12 files changed, 210 insertions(+), 21 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/account/account.module.ts diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 87c6561315..f1af3b745a 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -244,7 +244,7 @@ export class AuthService { if (authState && authState.authUser) { if (authState.authUser.authority === Authority.TENANT_ADMIN || authState.authUser.authority === Authority.CUSTOMER_USER) { if ((this.userHasDefaultDashboard(authState) && authState.forceFullscreen) || authState.authUser.isPublic) { - if (path === 'profile' || path === 'security') { + if (path.startsWith('account')) { if (this.userHasProfile(authState.authUser)) { return false; } else { diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index b33c552eb1..507ed01984 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -262,6 +262,34 @@ export class MenuService { isMdiIcon: true } ] + }, + { + id: 'account', + name: 'profile.profile', + type: 'link', + path: '/account', + disabled: true, + icon: 'mdi:message-badge', + isMdiIcon: true, + pages: [ + { + id: 'personal_info', + name: 'account.personal-info', + fullName: 'account.personal-info', + type: 'link', + path: '/account/profile', + icon: 'mdi:badge-account-horizontal', + isMdiIcon: true + }, + { + id: 'security', + name: 'security.security', + fullName: 'security.security', + type: 'link', + path: '/account/security', + icon: 'lock' + } + ] } ); return sections; @@ -634,6 +662,34 @@ export class MenuService { icon: 'track_changes' } ] + }, + { + id: 'account', + name: 'profile.profile', + type: 'link', + path: '/account', + disabled: true, + icon: 'mdi:message-badge', + isMdiIcon: true, + pages: [ + { + id: 'personal_info', + name: 'account.personal-info', + fullName: 'account.personal-info', + type: 'link', + path: '/account/profile', + icon: 'mdi:badge-account-horizontal', + isMdiIcon: true + }, + { + id: 'security', + name: 'security.security', + fullName: 'security.security', + type: 'link', + path: '/account/security', + icon: 'lock' + } + ] } ); return sections; @@ -885,6 +941,34 @@ export class MenuService { icon: 'inbox' } ] + }, + { + id: 'account', + name: 'profile.profile', + type: 'link', + path: '/account', + disabled: true, + icon: 'mdi:message-badge', + isMdiIcon: true, + pages: [ + { + id: 'personal_info', + name: 'account.personal-info', + fullName: 'account.personal-info', + type: 'link', + path: '/account/profile', + icon: 'mdi:badge-account-horizontal', + isMdiIcon: true + }, + { + id: 'security', + name: 'security.security', + fullName: 'security.security', + type: 'link', + path: '/account/security', + icon: 'lock' + } + ] } ); return sections; diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts index 6e045b9786..1ab9cea1dc 100644 --- a/ui-ngx/src/app/modules/home/home.component.ts +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { AfterViewInit, Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { fromEvent } from 'rxjs'; import { Store } from '@ngrx/store'; import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; @@ -27,10 +27,10 @@ import { MediaBreakpoints } from '@shared/models/constants'; import screenfull from 'screenfull'; import { MatSidenav } from '@angular/material/sidenav'; import { AuthState } from '@core/auth/auth.models'; -import { WINDOW } from '@core/services/window.service'; import { instanceOfSearchableComponent, ISearchableComponent } from '@home/models/searchable-component.models'; import { ActiveComponentService } from '@core/services/active-component.service'; import { RouterTabsComponent } from '@home/components/router-tabs.component'; +import { Router } from '@angular/router'; @Component({ selector: 'tb-home', @@ -65,8 +65,8 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni hideLoadingBar = false; constructor(protected store: Store, - @Inject(WINDOW) private window: Window, private activeComponentService: ActiveComponentService, + private router: Router, public breakpointObserver: BreakpointObserver) { super(store); } @@ -120,7 +120,8 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni } goBack() { - this.window.history.back(); + const dashboardId = this.authState.userDetails.additionalInfo.defaultDashboardId; + this.router.navigate(['dashboard', dashboardId]).then(() => {}); } activeComponentChanged(activeComponent: any) { diff --git a/ui-ngx/src/app/modules/home/menu/side-menu.component.ts b/ui-ngx/src/app/modules/home/menu/side-menu.component.ts index f6e1f30624..cf3e5ca4db 100644 --- a/ui-ngx/src/app/modules/home/menu/side-menu.component.ts +++ b/ui-ngx/src/app/modules/home/menu/side-menu.component.ts @@ -17,6 +17,8 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { MenuService } from '@core/services/menu.service'; import { MenuSection } from '@core/services/menu.models'; +import { Observable, of } from 'rxjs'; +import { mergeMap, share } from 'rxjs/operators'; @Component({ selector: 'tb-side-menu', @@ -26,15 +28,23 @@ import { MenuSection } from '@core/services/menu.models'; }) export class SideMenuComponent implements OnInit { - menuSections$ = this.menuService.menuSections(); + menuSections$: Observable>; constructor(private menuService: MenuService) { + this.menuSections$ = this.menuService.menuSections().pipe( + mergeMap((sections) => this.filterSections(sections)), + share() + ); } trackByMenuSection(index: number, section: MenuSection){ return section.id; } + private filterSections(sections: Array): Observable> { + return of(sections.filter(section => !section.disabled)); + } + ngOnInit() { } diff --git a/ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts b/ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts new file mode 100644 index 0000000000..bb63b361b6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts @@ -0,0 +1,54 @@ +/// +/// 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. +/// + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { RouterTabsComponent } from '@home/components/router-tabs.component'; +import { Authority } from '@shared/models/authority.enum'; +import { securityRoutes } from '@home/pages/security/security-routing.module'; +import { profileRoutes } from '@home/pages/profile/profile-routing.module'; + +const routes: Routes = [ + { + path: 'account', + component: RouterTabsComponent, + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + breadcrumb: { + label: 'account.account', + icon: 'account_circle' + } + }, + children: [ + { + path: '', + children: [], + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + redirectTo: '/account/profile', + } + }, + ...profileRoutes, + ...securityRoutes + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class AccountRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/account/account.module.ts b/ui-ngx/src/app/modules/home/pages/account/account.module.ts new file mode 100644 index 0000000000..df178607ce --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/account/account.module.ts @@ -0,0 +1,28 @@ +/// +/// 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. +/// + +import { NgModule } from '@angular/core'; +import { AccountRoutingModule } from '@home/pages/account/account-routing.module'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + declarations: [ ], + imports: [ + CommonModule, + AccountRoutingModule + ] +}) +export class AccountModule { } diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts index b1f4715c33..005c47f787 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -42,6 +42,7 @@ import { AlarmModule } from '@home/pages/alarm/alarm.module'; import { EntitiesModule } from '@home/pages/entities/entities.module'; import { FeaturesModule } from '@home/pages/features/features.module'; import { NotificationModule } from '@home/pages/notification/notification.module'; +import { AccountModule } from '@home/pages/account/account.module'; @NgModule({ exports: [ @@ -70,7 +71,8 @@ import { NotificationModule } from '@home/pages/notification/notification.module ApiUsageModule, OtaUpdateModule, UserModule, - VcModule + VcModule, + AccountModule ] }) export class HomePagesModule { } diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts b/ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts index c14e450745..334174194c 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts @@ -40,7 +40,7 @@ export class UserProfileResolver implements Resolve { } } -const routes: Routes = [ +export const profileRoutes: Routes = [ { path: 'profile', component: ProfileComponent, @@ -59,6 +59,13 @@ const routes: Routes = [ } ]; +const routes: Routes = [ + { + path: 'profile', + redirectTo: 'account/profile' + } +]; + @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], diff --git a/ui-ngx/src/app/modules/home/pages/security/security-routing.module.ts b/ui-ngx/src/app/modules/home/pages/security/security-routing.module.ts index d2820e0184..f6da1dabd2 100644 --- a/ui-ngx/src/app/modules/home/pages/security/security-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/security/security-routing.module.ts @@ -53,7 +53,7 @@ export class UserTwoFAProvidersResolver implements Resolve
- - +
diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts index e9d67a3fdd..13a7ef0a70 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts @@ -38,7 +38,7 @@ import { TranslateService } from '@ngx-translate/core'; import { MatDialog } from '@angular/material/dialog'; import { DialogService } from '@core/services/dialog.service'; import { Direction, SortOrder } from '@shared/models/page/sort-order'; -import { fromEvent, merge } from 'rxjs'; +import { fromEvent, merge, Observable } from 'rxjs'; import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; import { EntityId } from '@shared/models/id/entity-id'; import { @@ -48,7 +48,7 @@ import { isClientSideTelemetryType, LatestTelemetry, TelemetryType, - telemetryTypeTranslations, + telemetryTypeTranslations, TimeseriesDeleteStrategy, toTelemetryType } from '@shared/models/telemetry/telemetry.models'; import { AttributeDatasource } from '@home/models/datasource/attribute-datasource'; @@ -82,10 +82,14 @@ import { AddWidgetToDashboardDialogComponent, AddWidgetToDashboardDialogData } from '@home/components/attribute/add-widget-to-dashboard-dialog.component'; -import { deepClone } from '@core/utils'; +import { deepClone, isUndefinedOrNull } from '@core/utils'; import { Filters } from '@shared/models/query/query.models'; import { hidePageSizePixelValue } from '@shared/models/constants'; import { ResizeObserver } from '@juggle/resize-observer'; +import { + DELETE_TIMESERIES_PANEL_DATA, + DeleteTimeseriesPanelComponent, DeleteTimeseriesPanelData +} from '@home/components/attribute/delete-timeseries-panel.component'; @Component({ @@ -378,6 +382,79 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI }); } + deleteTimeseries($event: Event, attribute?: AttributeData) { + if ($event) { + $event.stopPropagation(); + } + const isMultipleDeletion = isUndefinedOrNull(attribute); + const target = $event.target || $event.srcElement || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'start', + originY: 'top', + overlayX: 'end', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + config.maxWidth = '488px'; + config.width = '100%'; + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + + const providers: StaticProvider[] = [ + { + provide: DELETE_TIMESERIES_PANEL_DATA, + useValue: { + isMultipleDeletion: isMultipleDeletion + } as DeleteTimeseriesPanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); + const componentRef = overlayRef.attach(new ComponentPortal(DeleteTimeseriesPanelComponent, + this.viewContainerRef, injector)); + componentRef.onDestroy(() => { + if (componentRef.instance.result !== null) { + const strategy = componentRef.instance.result; + const timeseries = isMultipleDeletion ? this.dataSource.selection.selected : [attribute]; + let deleteAllDataForKeys = false; + let rewriteLatestIfDeleted = false; + let startTs = null; + let endTs = null; + let deleteLatest = false; + let task: Observable; + if (strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY) { + deleteAllDataForKeys = true; + deleteLatest = true; + } + if (strategy === TimeseriesDeleteStrategy.DELETE_OLD_DATA_EXCEPT_LATEST_VALUE) { + deleteAllDataForKeys = true; + } + if (strategy === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE) { + task = this.attributeService.deleteEntityLatestTimeseries(this.entityIdValue, timeseries); + } + if (strategy === TimeseriesDeleteStrategy.DELETE_DATA_FOR_TIME_PERIOD) { + startTs = componentRef.instance.startDateTime.getTime(); + endTs = componentRef.instance.endDateTime.getTime(); + rewriteLatestIfDeleted = componentRef.instance.rewriteLatestIfDeleted; + } + if (!task) { + task = this.attributeService.deleteEntityTimeseries(this.entityIdValue, timeseries, deleteAllDataForKeys, + startTs, endTs, rewriteLatestIfDeleted, deleteLatest); + } + task.subscribe(() => this.reloadAttributes()); + } + }); + } + deleteAttributes($event: Event) { if ($event) { $event.stopPropagation(); diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html new file mode 100644 index 0000000000..e164cb56ed --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html @@ -0,0 +1,74 @@ + + +
+ +

{{ "attribute.delete-timeseries.delete-strategy" | translate }}

+ + +
+
+ + attribute.delete-timeseries.strategy + + + {{ strategiesTranslationsMap.get(strategy) | translate }} + + + +
+
+ + attribute.delete-timeseries.start-time + + + + + + attribute.delete-timeseries.ends-on + + + + +
+ + {{ "attribute.delete-timeseries.rewrite-latest-value-if-deleted" | translate }} + +
+
+
+ + + +
+
+ diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss new file mode 100644 index 0000000000..c0f26644d5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss @@ -0,0 +1,28 @@ +/** + * 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. + */ + +:host { + width: 100%; + background-color: #fff; + box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3), 0px 2px 6px 2px rgba(0, 0, 0, 0.15); + border-radius: 4px; +} + +:host ::ng-deep{ + div .mat-toolbar { + background: none; + } +} diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts new file mode 100644 index 0000000000..914e5246f7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts @@ -0,0 +1,100 @@ +/// +/// 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. +/// + +import { Component, Inject, InjectionToken, OnInit } from '@angular/core'; +import { OverlayRef } from '@angular/cdk/overlay'; +import { + TimeseriesDeleteStrategy, + timeseriesDeleteStrategyTranslations +} from '@shared/models/telemetry/telemetry.models'; +import { MINUTE } from '@shared/models/time/time.models'; + +export const DELETE_TIMESERIES_PANEL_DATA = new InjectionToken('DeleteTimeseriesPanelData'); + +export interface DeleteTimeseriesPanelData { + isMultipleDeletion: boolean; +} + +@Component({ + selector: 'tb-delete-timeseries-panel', + templateUrl: './delete-timeseries-panel.component.html', + styleUrls: ['./delete-timeseries-panel.component.scss'] +}) +export class DeleteTimeseriesPanelComponent implements OnInit { + + strategy: string = TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY; + + result: string = null; + + startDateTime: Date; + + endDateTime: Date; + + rewriteLatestIfDeleted: boolean = false; + + strategiesTranslationsMap = timeseriesDeleteStrategyTranslations; + + multipleDeletionStrategies = [ + TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY, + TimeseriesDeleteStrategy.DELETE_OLD_DATA_EXCEPT_LATEST_VALUE + ]; + + constructor(@Inject(DELETE_TIMESERIES_PANEL_DATA) public data: DeleteTimeseriesPanelData, + public overlayRef: OverlayRef) { } + + ngOnInit(): void { + let today = new Date(); + this.startDateTime = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()); + this.endDateTime = today; + if (this.data.isMultipleDeletion) { + this.strategiesTranslationsMap = new Map(Array.from(this.strategiesTranslationsMap.entries()) + .filter(([strategy]) => { + return this.multipleDeletionStrategies.includes(strategy); + })) + } + } + + delete(): void { + this.result = this.strategy; + this.overlayRef.dispose(); + } + + cancel(): void { + this.overlayRef.dispose(); + } + + isPeriodStrategy(): boolean { + return this.strategy === TimeseriesDeleteStrategy.DELETE_DATA_FOR_TIME_PERIOD; + } + + onStartDateTimeChange(newStartDateTime: Date) { + const endDateTimeTs = this.endDateTime.getTime(); + if (newStartDateTime.getTime() >= endDateTimeTs) { + this.startDateTime = new Date(endDateTimeTs - MINUTE); + } else { + this.startDateTime = newStartDateTime; + } + } + + onEndDateTimeChange(newEndDateTime: Date) { + const startDateTimeTs = this.startDateTime.getTime(); + if (newEndDateTime.getTime() <= startDateTimeTs) { + this.endDateTime = new Date(startDateTimeTs + MINUTE); + } else { + this.endDateTime = newEndDateTime; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index a6e2cc03dc..a18f785b35 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -177,6 +177,7 @@ import { } from '@home/components/widget/action/manage-widget-actions-dialog.component'; import { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module'; import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/basic-widget-config.module'; +import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delete-timeseries-panel.component'; @NgModule({ declarations: @@ -205,6 +206,7 @@ import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/ba AttributeTableComponent, AddAttributeDialogComponent, EditAttributeValuePanelComponent, + DeleteTimeseriesPanelComponent, AliasesEntitySelectPanelComponent, AliasesEntitySelectComponent, AliasesEntityAutocompleteComponent, diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 50cbef2f8b..76f6b0f247 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -61,6 +61,13 @@ export enum TelemetryFeature { TIMESERIES = 'TIMESERIES' } +export enum TimeseriesDeleteStrategy { + DELETE_ALL_DATA_INCLUDING_KEY = 'DELETE_ALL_DATA_INCLUDING_KEY', + DELETE_OLD_DATA_EXCEPT_LATEST_VALUE = 'DELETE_OLD_DATA_EXCEPT_LATEST_VALUE', + DELETE_LATEST_VALUE = 'DELETE_LATEST_VALUE', + DELETE_DATA_FOR_TIME_PERIOD = 'DELETE_DATA_FOR_TIME_PERIOD' +} + export type TelemetryType = LatestTelemetry | AttributeScope; export const toTelemetryType = (val: string): TelemetryType => { @@ -73,7 +80,7 @@ export const toTelemetryType = (val: string): TelemetryType => { export const telemetryTypeTranslations = new Map( [ - [LatestTelemetry.LATEST_TELEMETRY, 'attribute.scope-latest-telemetry'], + [LatestTelemetry.LATEST_TELEMETRY, 'attribute.scope-telemetry'], [AttributeScope.CLIENT_SCOPE, 'attribute.scope-client'], [AttributeScope.SERVER_SCOPE, 'attribute.scope-server'], [AttributeScope.SHARED_SCOPE, 'attribute.scope-shared'] @@ -89,6 +96,15 @@ export const isClientSideTelemetryType = new Map( ] ); +export const timeseriesDeleteStrategyTranslations = new Map( + [ + [TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY, 'attribute.delete-timeseries.all-data-including-key'], + [TimeseriesDeleteStrategy.DELETE_OLD_DATA_EXCEPT_LATEST_VALUE, 'attribute.delete-timeseries.old-data-except-latest'], + [TimeseriesDeleteStrategy.DELETE_LATEST_VALUE, 'attribute.delete-timeseries.latest-value'], + [TimeseriesDeleteStrategy.DELETE_DATA_FOR_TIME_PERIOD, 'attribute.delete-timeseries.data-for-time-period'] + ] +) + export interface AttributeData { lastUpdateTs?: number; key: string; diff --git a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json index 349d13da2c..edb406d9cb 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json @@ -632,7 +632,7 @@ "attributes": "Atributs", "latest-telemetry": "Última telemetria", "attributes-scope": "Abast dels atributs del dispositiu", - "scope-latest-telemetry": "Última telemetria", + "scope-telemetry": "Telemetria", "scope-client": "Atributs del Client", "scope-server": "Atributs del Servidor", "scope-shared": "Atributs Compartits", diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index 52873b4d70..5697486e37 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -445,7 +445,7 @@ "attributes": "Atributy", "latest-telemetry": "Poslední telemetrie", "attributes-scope": "Rozsah atributů entity", - "scope-latest-telemetry": "Poslední telemetrie", + "scope-telemetry": "Telemetrie", "scope-client": "Atributy klienta", "scope-server": "Atributy serveru", "scope-shared": "Sdílené atributy", diff --git a/ui-ngx/src/assets/locale/locale.constant-da_DK.json b/ui-ngx/src/assets/locale/locale.constant-da_DK.json index 2c1df70902..1486870a7a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-da_DK.json +++ b/ui-ngx/src/assets/locale/locale.constant-da_DK.json @@ -453,7 +453,7 @@ "attributes": "Attributter", "latest-telemetry": "Seneste telemetri", "attributes-scope": "Omfang af entitetsattributter", - "scope-latest-telemetry": "Seneste telemetri", + "scope-telemetry": "Telemetri", "scope-client": "Klientattributter", "scope-server": "Serverattributter", "scope-shared": "Delte attributter", diff --git a/ui-ngx/src/assets/locale/locale.constant-de_DE.json b/ui-ngx/src/assets/locale/locale.constant-de_DE.json index ed73ad25cf..c27d63f4fb 100644 --- a/ui-ngx/src/assets/locale/locale.constant-de_DE.json +++ b/ui-ngx/src/assets/locale/locale.constant-de_DE.json @@ -324,7 +324,7 @@ "attributes": "Eigenschaften", "latest-telemetry": "Neueste Telemetrie", "attributes-scope": "Entitätseigenschaftsbereich", - "scope-latest-telemetry": "Neueste Telemetrie", + "scope-telemetry": "Telemetrie", "scope-client": "Client Eigenschaften", "scope-server": "Server Eigenschaften", "scope-shared": "Gemeinsame Eigenschaften", diff --git a/ui-ngx/src/assets/locale/locale.constant-el_GR.json b/ui-ngx/src/assets/locale/locale.constant-el_GR.json index 453b5dd83c..36e8e4e000 100644 --- a/ui-ngx/src/assets/locale/locale.constant-el_GR.json +++ b/ui-ngx/src/assets/locale/locale.constant-el_GR.json @@ -291,7 +291,7 @@ "attributes": "Χαρακτηριστικά", "latest-telemetry": "Τελευταία τηλεμετρία", "attributes-scope": "Πεδίο εφαρμογής Χαρακτηριστικών Οντότητας", - "scope-latest-telemetry": "Τελευταία τηλεμετρία", + "scope-telemetry": "Τηλεμετρία", "scope-client": "Χαρακτηριστικά Client", "scope-server": "Χαρακτηριστικά Server", "scope-shared": "Κοινόχρηστα Χαρακτηριστικά", diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 82f10b7f4c..091ee1e3bf 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -691,7 +691,7 @@ "attributes": "Attributes", "latest-telemetry": "Latest telemetry", "attributes-scope": "Entity attributes scope", - "scope-latest-telemetry": "Latest telemetry", + "scope-telemetry": "Telemetry", "scope-client": "Client attributes", "scope-server": "Server attributes", "scope-shared": "Shared attributes", @@ -717,7 +717,18 @@ "no-attributes-text": "No attributes found", "no-telemetry-text": "No telemetry found", "copy-key": "Copy key", - "copy-value": "Copy value" + "copy-value": "Copy value", + "delete-timeseries": { + "start-time": "Start time", + "ends-on": "Ends on", + "strategy": "Strategy", + "delete-strategy": "Delete strategy", + "all-data-including-key": "Delete all data including key", + "old-data-except-latest": "Delete old data except latest value", + "latest-value": "Delete latest value", + "data-for-time-period": "Delete data for time period", + "rewrite-latest-value-if-deleted": "Rewrite latest value if deleted" + } }, "api-usage": { "api-features": "API features", diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index 6518e03f58..62152e998d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -667,7 +667,7 @@ "attributes": "Atributos", "latest-telemetry": "Última telemetría", "attributes-scope": "Alcance de los atributos del dispositivo", - "scope-latest-telemetry": "Última telemetría", + "scope-telemetry": "Telemetría", "scope-client": "Atributos de Cliente", "scope-server": "Atributos de Servidor", "scope-shared": "Atributos Compartidos", diff --git a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json index 6e5026b011..da841a6e53 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json @@ -254,7 +254,7 @@ "attributes": "ويژگي ها", "latest-telemetry": "آخرين سنجش", "attributes-scope": "حوزه ويژگي هاي موجودي", - "scope-latest-telemetry": "آخرين سنجش", + "scope-telemetry": "تله متری", "scope-client": "ويژگي هاي مشتري", "scope-server": "ويژگي هاي سِروِر", "scope-shared": "ويژگي هاي مشترک", diff --git a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json index 19f92e5a7c..a929477d5e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json @@ -459,7 +459,7 @@ "next-widget": "Widget suivant", "prev-widget": "Widget précédent", "scope-client": "Attributs du client", - "scope-latest-telemetry": "Dernière télémétrie", + "scope-telemetry": "Télémétrie", "scope-server": "Attributs du serveur", "scope-shared": "Attributs partagés", "selected-attributes": "{count, plural, =1 {1 attribut} other {# attributs} } sélectionnés", diff --git a/ui-ngx/src/assets/locale/locale.constant-it_IT.json b/ui-ngx/src/assets/locale/locale.constant-it_IT.json index 94acd17f82..2c093e76a9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-it_IT.json +++ b/ui-ngx/src/assets/locale/locale.constant-it_IT.json @@ -276,7 +276,7 @@ "attributes": "Attributi", "latest-telemetry": "Ultima telemetria", "attributes-scope": "Visibilità attributi entità", - "scope-latest-telemetry": "Ultima telemetria", + "scope-telemetry": "Telemetria", "scope-client": "Attributi client", "scope-server": "Attributi server", "scope-shared": "Attributi condivisi", diff --git a/ui-ngx/src/assets/locale/locale.constant-ja_JP.json b/ui-ngx/src/assets/locale/locale.constant-ja_JP.json index f63a6681a2..23145c1f89 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ja_JP.json +++ b/ui-ngx/src/assets/locale/locale.constant-ja_JP.json @@ -244,7 +244,7 @@ "attributes": "属性", "latest-telemetry": "最新テレメトリ", "attributes-scope": "エンティティ属性のスコープ", - "scope-latest-telemetry": "最新テレメトリ", + "scope-telemetry": "テレメトリー", "scope-client": "クライアントの属性", "scope-server": "サーバーの属性", "scope-shared": "共有属性", diff --git a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json index a6c5a6576d..89d25703e3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json +++ b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json @@ -290,7 +290,7 @@ "attributes": "ატრიბუტები", "latest-telemetry": "უახლესი ტელემეტრია", "attributes-scope": "ობიექტის ატრიბუტების ფარგლები", - "scope-latest-telemetry": "უახლესი ტელემეტრია", + "scope-telemetry": "ტელემეტრია", "scope-client": "კლიენტის ატრიბუტები", "scope-server": "სერვერის ატრიბუტები", "scope-shared": "ატრიბუტების გაზიარება", diff --git a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json index 758482f578..3da051ec1a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json +++ b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json @@ -408,7 +408,7 @@ "attributes": "속성", "latest-telemetry": "최근 데이터", "attributes-scope": "장치 속성 범위", - "scope-latest-telemetry": "최근 데이터", + "scope-telemetry": "원격 측정", "scope-client": "클라이언트 속성", "scope-server": "서버 속성", "scope-shared": "공유 속성", diff --git a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json index d584f2da96..f4f5befc14 100644 --- a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json +++ b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json @@ -256,7 +256,7 @@ "attributes": "Attribūti", "latest-telemetry": "Jaunākā telemetrija", "attributes-scope": "Vienības atribūtu darbības joma", - "scope-latest-telemetry": "Jaunākā telemetrija", + "scope-telemetry": "Telemetrija", "scope-client": "Klientu atribūti", "scope-server": "Servera atribūti", "scope-shared": "Dalītie atribūti", diff --git a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json index 2bba0338d2..28c7082df9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json +++ b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json @@ -309,7 +309,7 @@ "attributes": "Atributos", "latest-telemetry": "Última telemetria", "attributes-scope": "Escopo de atributos de entidade", - "scope-latest-telemetry": "Última telemetria", + "scope-telemetry": "Telemetria", "scope-client": "Atributos do cliente", "scope-server": "Atributos do servidor", "scope-shared": "Atributos compartilhados", diff --git a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json index fa5cee48c9..da0012e6f6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json +++ b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json @@ -285,7 +285,7 @@ "attributes": "Atribute", "latest-telemetry": "Ultimele Date Telemetrice", "attributes-scope": "Scop Atribute Entitate", - "scope-latest-telemetry": "Ultimele Date Telemetrice", + "scope-telemetry": "Telemetrie", "scope-client": "Atribute Client", "scope-server": "Atribute Server", "scope-shared": "Atribute Partajate", diff --git a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json index 8aced0ddc6..e53e4fc7c0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json +++ b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json @@ -408,7 +408,7 @@ "attributes": "Lastnosti", "latest-telemetry": "Najnovejša telemetrija", "attributes-scope": "Obseg atributov entitete", - "scope-latest-telemetry": "Najnovejša telemetrija", + "scope-telemetry": "Telemetrija", "scope-client": "Atributi odjemalca", "scope-server": "Atributi strežnika", "scope-shared": "Skupni atributi", diff --git a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json index b175a2d51a..ae79f0c81c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json +++ b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json @@ -445,7 +445,7 @@ "attributes": "Öznitelikler", "latest-telemetry": "Son telemetri", "attributes-scope": "Varlık öznitelik kapsamı", - "scope-latest-telemetry": "Son telemetri", + "scope-telemetry": "telemetri", "scope-client": "İstemci öznitelikler", "scope-server": "Sunucu öznitelikler", "scope-shared": "Paylaşılan öznitelikler", diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json index 7aa541e57f..bd608cd709 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -342,7 +342,7 @@ "attributes": "Атрибути", "latest-telemetry": "Остання телеметрія", "attributes-scope": "Область видимості атрибутів", - "scope-latest-telemetry": "Остання телеметрія", + "scope-telemetry": "Телеметрія", "scope-client": "Клієнтські атрибути", "scope-server": "Серверні атрибути", "scope-shared": "Спільні атрибути", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index b39e10e46c..4c33ac46a2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -590,7 +590,7 @@ "attributes": "属性", "latest-telemetry": "最新遥测数据", "attributes-scope": "设备属性范围", - "scope-latest-telemetry": "最新遥测数据", + "scope-telemetry": "遥测", "scope-client": "客户端属性", "scope-server": "服务端属性", "scope-shared": "共享属性", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json index f2cce81824..3a1c4edd83 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json @@ -519,7 +519,7 @@ "attributes": "屬性", "latest-telemetry": "最新遙測", "attributes-scope": "設備屬性範圍", - "scope-latest-telemetry": "最新遙測", + "scope-telemetry": "遙測", "scope-client": "客戶端屬性", "scope-server": "服務端屬性", "scope-shared": "共享屬性", From 5eebbf89859ee08ee6c481646c060495f51086ce Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 13 Jul 2023 22:28:59 +0200 Subject: [PATCH 040/166] web socket handler tests added. ws msg queue fixed the last msg pickup (and msg order as result) --- .../controller/plugin/TbWebSocketHandler.java | 44 +++-- .../plugin/TbWebSocketHandlerTest.java | 160 ++++++++++++++++++ 2 files changed, 186 insertions(+), 18 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 56d88143a5..481d412d59 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -219,12 +219,12 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke .build(); } - private class SessionMetaData implements SendHandler { + class SessionMetaData implements SendHandler { private final WebSocketSession session; private final RemoteEndpoint.Async asyncRemote; private final WebSocketSessionRef sessionRef; - private final AtomicBoolean isSending = new AtomicBoolean(false); + final AtomicBoolean isSending = new AtomicBoolean(false); private final Queue> msgQueue; private volatile long lastActivityTime; @@ -254,11 +254,13 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } } - private void closeSession(CloseStatus reason) { + void closeSession(CloseStatus reason) { try { close(this.sessionRef, reason); } catch (IOException ioe) { log.trace("[{}] Session transport error", session.getId(), ioe); + } finally { + msgQueue.clear(); } } @@ -271,20 +273,19 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } void sendMsg(TbWebSocketMsg msg) { - if (isSending.compareAndSet(false, true)) { - sendMsgInternal(msg); - } else { - try { - msgQueue.add(msg); - } catch (RuntimeException e) { - if (log.isTraceEnabled()) { - log.trace("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId(), e); - } else { - log.info("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId()); - } - closeSession(CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!")); + try { + msgQueue.add(msg); + } catch (RuntimeException e) { + if (log.isTraceEnabled()) { + log.trace("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId(), e); + } else { + log.info("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId()); } + closeSession(CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!")); + return; } + + processNextMsg(); } private void sendMsgInternal(TbWebSocketMsg msg) { @@ -292,9 +293,11 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke if (TbWebSocketMsgType.TEXT.equals(msg.getType())) { TbWebSocketTextMsg textMsg = (TbWebSocketTextMsg) msg; this.asyncRemote.sendText(textMsg.getMsg(), this); + // isSending status will be reset in the onResult method by call back } else { TbWebSocketPingMsg pingMsg = (TbWebSocketPingMsg) msg; - this.asyncRemote.sendPing(pingMsg.getMsg()); + this.asyncRemote.sendPing(pingMsg.getMsg()); // blocking call + isSending.set(false); processNextMsg(); } } catch (Exception e) { @@ -308,12 +311,17 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke if (!result.isOK()) { log.trace("[{}] Failed to send msg", session.getId(), result.getException()); closeSession(CloseStatus.SESSION_NOT_RELIABLE); - } else { - processNextMsg(); + return; } + + isSending.set(false); + processNextMsg(); } private void processNextMsg() { + if (msgQueue.isEmpty() || !isSending.compareAndSet(false, true)) { + return; + } TbWebSocketMsg msg = msgQueue.poll(); if (msg != null) { sendMsgInternal(msg); diff --git a/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java b/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java new file mode 100644 index 0000000000..0394e8a505 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java @@ -0,0 +1,160 @@ +/** + * 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.controller.plugin; + +import lombok.extern.slf4j.Slf4j; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.adapter.NativeWebSocketSession; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.service.ws.WebSocketSessionRef; + +import javax.websocket.RemoteEndpoint; +import javax.websocket.SendHandler; +import javax.websocket.SendResult; +import javax.websocket.Session; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.willAnswer; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@Slf4j +class TbWebSocketHandlerTest { + + TbWebSocketHandler wsHandler; + NativeWebSocketSession session; + Session nativeSession; + RemoteEndpoint.Async asyncRemote; + WebSocketSessionRef sessionRef; + int maxMsgQueuePerSession; + TbWebSocketHandler.SessionMetaData sendHandler; + ExecutorService executor; + + @BeforeEach + void setUp() throws IOException { + maxMsgQueuePerSession = 100; + executor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName(getClass().getSimpleName())); + wsHandler = spy(new TbWebSocketHandler()); + willDoNothing().given(wsHandler).close(any(), any()); + session = mock(NativeWebSocketSession.class); + nativeSession = mock(Session.class); + willReturn(nativeSession).given(session).getNativeSession(Session.class); + asyncRemote = mock(RemoteEndpoint.Async.class); + willReturn(asyncRemote).given(nativeSession).getAsyncRemote(); + sessionRef = mock(WebSocketSessionRef.class, Mockito.RETURNS_DEEP_STUBS); //prevent NPE on logs + sendHandler = spy(wsHandler.new SessionMetaData(session, sessionRef, maxMsgQueuePerSession)); + } + + @AfterEach + void tearDown() { + if (executor != null) { + executor.shutdownNow(); + } + } + + @Test + void sendHandler_sendMsg_parallel_no_race() throws InterruptedException { + CountDownLatch finishLatch = new CountDownLatch(maxMsgQueuePerSession * 2); + AtomicInteger sendersCount = new AtomicInteger(); + willAnswer(invocation -> { + assertThat(sendersCount.incrementAndGet()).as("no race").isEqualTo(1); + String text = invocation.getArgument(0); + SendHandler onResultHandler = invocation.getArgument(1); + SendResult sendResult = new SendResult(); + executor.submit(() -> { + sendersCount.decrementAndGet(); + onResultHandler.onResult(sendResult); + finishLatch.countDown(); + }); + return null; + }).given(asyncRemote).sendText(anyString(), any()); + + assertThat(sendHandler.isSending.get()).as("sendHandler not is in sending state").isFalse(); + //first batch + IntStream.range(0, maxMsgQueuePerSession).parallel().forEach(i -> sendHandler.sendMsg("hello " + i)); + Awaitility.await("first batch processed").atMost(30, TimeUnit.SECONDS).until(() -> finishLatch.getCount() == maxMsgQueuePerSession); + assertThat(sendHandler.isSending.get()).as("sendHandler not is in sending state").isFalse(); + //second batch - to test pause between big msg batches + IntStream.range(100, 100 + maxMsgQueuePerSession).parallel().forEach(i -> sendHandler.sendMsg("hello " + i)); + assertThat(finishLatch.await(30, TimeUnit.SECONDS)).as("all callbacks fired").isTrue(); + + verify(sendHandler, never()).closeSession(any()); + verify(sendHandler, times(maxMsgQueuePerSession * 2)).onResult(any()); + assertThat(sendHandler.isSending.get()).as("sendHandler not is in sending state").isFalse(); + } + + @Test + void sendHandler_sendMsg_message_order() throws InterruptedException { + CountDownLatch finishLatch = new CountDownLatch(maxMsgQueuePerSession); + Collection outputs = new ConcurrentLinkedQueue<>(); + willAnswer(invocation -> { + String text = invocation.getArgument(0); + outputs.add(text); + SendHandler onResultHandler = invocation.getArgument(1); + SendResult sendResult = new SendResult(); + executor.submit(() -> { + onResultHandler.onResult(sendResult); + finishLatch.countDown(); + }); + return null; + }).given(asyncRemote).sendText(anyString(), any()); + + List inputs = IntStream.range(0, maxMsgQueuePerSession).mapToObj(i -> "msg " + i).collect(Collectors.toList()); + inputs.forEach(s -> sendHandler.sendMsg(s)); + + assertThat(finishLatch.await(30, TimeUnit.SECONDS)).as("all callbacks fired").isTrue(); + assertThat(outputs).as("inputs exactly the same as outputs").containsExactlyElementsOf(inputs); + + verify(sendHandler, never()).closeSession(any()); + verify(sendHandler, times(maxMsgQueuePerSession)).onResult(any()); + } + + @Test + void sendHandler_sendMsg_queue_size_exceed() { + willDoNothing().given(asyncRemote).sendText(anyString(), any()); // send text will never call back, so queue will grow each sendMsg + sendHandler.sendMsg("first message to stay in-flight all the time during this test"); + IntStream.range(0, maxMsgQueuePerSession).parallel().forEach(i -> sendHandler.sendMsg("hello " + i)); + verify(sendHandler, never()).closeSession(any()); + sendHandler.sendMsg("excessive message"); + verify(sendHandler, times(1)).closeSession(eq(new CloseStatus(1008, "Max pending updates limit reached!"))); + verify(asyncRemote, times(1)).sendText(anyString(), any()); + } + +} From 32c2d44b0cd664aa832c4471c976690208dfa2be Mon Sep 17 00:00:00 2001 From: Ruslan Vasylkiv <87172504+rusikv@users.noreply.github.com> Date: Fri, 14 Jul 2023 12:37:40 +0300 Subject: [PATCH 041/166] added rewrite param to delete latest timeseries, enabled single selection deletion (#8933) --- ui-ngx/src/app/core/http/attribute.service.ts | 10 ++++++---- .../attribute/attribute-table.component.html | 2 +- .../components/attribute/attribute-table.component.ts | 11 ++++++----- .../attribute/delete-timeseries-panel.component.html | 2 ++ .../attribute/delete-timeseries-panel.component.ts | 6 +++++- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index b772cd63e6..cc20069e04 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -50,7 +50,7 @@ export class AttributeService { } public deleteEntityTimeseries(entityId: EntityId, timeseries: Array, deleteAllDataForKeys = false, - startTs?: number, endTs?: number, rewriteLatestIfDeleted = false, deleteLatest = false, + startTs?: number, endTs?: number, rewriteLatestIfDeleted = false, deleteLatest = true, config?: RequestConfig): Observable { const keys = timeseries.map(attribute => encodeURIComponent(attribute.key)).join(','); let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` + @@ -64,9 +64,11 @@ export class AttributeService { return this.http.delete(url, defaultHttpOptionsFromConfig(config)); } - public deleteEntityLatestTimeseries(entityId: EntityId, timeseries: Array, config?: RequestConfig): Observable { + public deleteEntityLatestTimeseries(entityId: EntityId, timeseries: Array, rewrite = true, + config?: RequestConfig): Observable { const keys = timeseries.map(attribute => encodeURIComponent(attribute.key)).join(','); - let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/latest/delete?keys=${keys}`; + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/latest/delete?keys=${keys}` + + `$rewrite=${rewrite}`; return this.http.delete(url, defaultHttpOptionsFromConfig(config)); } @@ -111,7 +113,7 @@ export class AttributeService { let deleteEntityTimeseriesObservable: Observable; if (deleteTimeseries.length) { deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, true, - null, null, false, false, config); + null, null, false, true, config); } else { deleteEntityTimeseriesObservable = of(null); } diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index 1def300b78..95ade10645 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -93,7 +93,7 @@ (click)="deleteAttributes($event)"> delete - + +
+
+ + + {{ deviceTransportTypeTranslationMap.get(BasicTransportType.HTTP) | translate }} + + + {{ deviceTransportTypeTranslationMap.get(DeviceTransportType.MQTT) | translate }} + + + {{ deviceTransportTypeTranslationMap.get(DeviceTransportType.COAP) | translate }} + + + {{ deviceTransportTypeTranslationMap.get(DeviceTransportType.SNMP) | translate }} + + + {{ deviceTransportTypeTranslationMap.get(DeviceTransportType.LWM2M) | translate }} + + +
+ + +
device.connectivity.use-following-instructions
+
+ device.connectivity.install-curl + +
+
+
device.connectivity.http-command
+ +
+
+
device.connectivity.https-command
+ +
+
+ +
+
device.connectivity.use-following-instructions
+
+ device.connectivity.install-mqtt-client + +
+
+
+
device.connectivity.mqtt-command
+ +
+
+
+
device.connectivity.mqtts-command
+ +
+ +
device.connectivity.mqtts-x509-command
+ +
+
+
+ +
+
device.connectivity.use-following-instructions
+
+ device.connectivity.install-coap-cli + +
+
+
+
device.connectivity.coap-command
+ +
+
+
+
device.connectivity.coaps-command
+ +
+ +
device.connectivity.coaps-x509-command
+ +
+
+
+ +
device.connectivity.snmp-command
+ +
+ +
device.connectivity.lwm2m-command
+ +
+
+
+
+
+
device.state
+
+ {{ (status ? 'device.active' : 'device.inactive') | translate }} +
+
+
attribute.latest-telemetry
+
+
+
device.time
+
attribute.key
+
attribute.value
+
+
+
+
{{ telemetry.lastUpdateTs | date: 'yyyy-MM-dd HH:mm:ss' }}
+
{{ telemetry.key }}
+
{{ telemetry.value }}
+
+
+
+
+
+
+
+ {{ 'action.dont-show-again' | translate}} + + +
+ +
+ + + {{ 'device.connectivity.loading-check-connectivity-command' | translate }} + +
+
+ +
+
+
attribute.no-latest-telemetry
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss new file mode 100644 index 0000000000..e7c88bb2cb --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss @@ -0,0 +1,151 @@ +/** + * 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. + */ + +@import "../../../../../scss/constants"; + +:host { + height: 100%; + max-height: 100vh; + display: grid; + grid-template-rows: min-content minmax(auto, 1fr) min-content; + + .tb-loader { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; + height: 300px; + max-height: 100%; + + .label { + margin-bottom: 0; + text-align: center; + } + } + + .status { + margin-left: 12px; + border-radius: 12px; + height: 24px; + line-height: 24px; + padding: 0 8px; + width: fit-content; + color: #198038; + background-color: rgba(25, 128, 56, 0.08); + font-size: 14px; + + &.inactive { + color: #d12730; + background-color: rgba(209, 39, 48, 0.08); + } + } + + .tb-hint-instruction { + border-radius: 6px; + background-color: rgba(48, 86, 128, 0.04); + padding: 6px 16px; + + .content { + vertical-align: middle; + } + } + + .tb-font-14 { + font-size: 14px; + } + + .tb-form-table-body { + max-height: 88px; + overflow-y: auto; + scrollbar-gutter: stable; + + .tb-form-table-row { + min-height: 38px; + } + } + + .tb-no-data-available { + .tb-no-data-bg { + min-height: 68px; + } + } + + @media #{$mat-sm} { + width: 470px; + } + + @media #{$mat-gt-sm} { + width: 720px; + } +} + +:host-context(.mat-mdc-dialog-container) { + .tb-dialog-actions { + display: flex; + gap: 8px; + padding: 8px 16px; + } + + .mat-mdc-dialog-content { + max-height: 80vh; + padding: 16px; + } +} + +:host ::ng-deep { + .tb-markdown-view { + .tb-command-code { + .code-wrapper { + padding: 0; + pre[class*=language-] { + background: #F3F6FA; + border-color: #305680; + } + } + button.clipboard-btn { + right: 0; + p { + color: #305680; + } + p, div { + background-color: #F3F6FA; + } + div { + img { + display: none; + } + &:after { + content: ""; + position: initial; + display: block; + width: 18px; + height: 18px; + background: #305680; + mask-image: url(/assets/copy-code-icon.svg); + mask-repeat: no-repeat; + } + } + } + } + } + .mdc-button__label > span { + .mat-icon { + vertical-align: text-bottom; + box-sizing: initial; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts new file mode 100644 index 0000000000..9e1639740e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts @@ -0,0 +1,170 @@ +/// +/// 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. +/// + +import { Component, Inject, NgZone, OnDestroy, OnInit } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { DeviceService } from '@core/http/device.service'; +import { FormBuilder } from '@angular/forms'; +import { + AttributeData, + AttributeScope, + AttributesSubscriptionCmd, + LatestTelemetry, + TelemetrySubscriber +} from '@shared/models/telemetry/telemetry.models'; +import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; +import { selectPersistDeviceStateToTelemetry } from '@core/auth/auth.selectors'; +import { take } from 'rxjs/operators'; +import { + BasicTransportType, + DeviceTransportType, + deviceTransportTypeTranslationMap, + NetworkTransportType +} from '@shared/models/device.models'; +import { UserSettingsService } from '@core/http/user-settings.service'; +import { ActionPreferencesUpdateUserSettings } from '@core/auth/auth.actions'; + +export interface DeviceCheckConnectivityDialogData { + deviceId: EntityId; + showDontShowAgain: boolean; +} +@Component({ + selector: 'tb-device-check-connectivity-dialog', + templateUrl: './device-check-connectivity-dialog.component.html', + styleUrls: ['./device-check-connectivity-dialog.component.scss'] +}) +export class DeviceCheckConnectivityDialogComponent extends + DialogComponent implements OnInit, OnDestroy { + + loadedCommand = false; + + status: boolean; + + latestTelemetry: Array = []; + + commands: {[key: string]: string}; + + allowTransportType = new Set(); + selectTransportType: NetworkTransportType; + + BasicTransportType = BasicTransportType; + DeviceTransportType = DeviceTransportType; + deviceTransportTypeTranslationMap = deviceTransportTypeTranslationMap; + + showDontShowAgain = this.data.showDontShowAgain; + + notShowAgain = false; + + private telemetrySubscriber: TelemetrySubscriber; + + private currentTime = Date.now(); + + private transportTypes = [...Object.keys(BasicTransportType), ...Object.keys(DeviceTransportType)] as Array; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) private data: DeviceCheckConnectivityDialogData, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private deviceService: DeviceService, + private telemetryWsService: TelemetryWebsocketService, + private userSettingsService: UserSettingsService, + private zone: NgZone) { + super(store, router, dialogRef); + } + + ngOnInit() { + this.loadCommands(); + this.subscribeToLatestTelemetry(); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.telemetrySubscriber?.complete(); + this.telemetrySubscriber?.unsubscribe(); + } + + close(): void { + if (this.notShowAgain && this.showDontShowAgain) { + this.store.dispatch(new ActionPreferencesUpdateUserSettings({ notDisplayConnectivityAfterAddDevice: true })); + this.dialogRef.close(null); + } else { + this.dialogRef.close(null); + } + } + + createMarkDownCommand(command: string): string { + return '```bash\n' + + command + + '{:copy-code}\n' + + '```'; + } + + private loadCommands() { + this.deviceService.getDevicePublishTelemetryCommands(this.data.deviceId.id).subscribe( + commands => { + this.commands = commands; + const commandsProtocols = Object.keys(commands); + this.transportTypes.forEach(transport => { + const findCommand = commandsProtocols.find(item => item.toUpperCase().startsWith(transport)); + if (findCommand) { + this.allowTransportType.add(transport); + } + }); + this.selectTransportType = this.allowTransportType.values().next().value; + this.loadedCommand = true; + } + ); + } + + private subscribeToLatestTelemetry() { + this.store.pipe(select(selectPersistDeviceStateToTelemetry)).pipe( + take(1) + ).subscribe(persistToTelemetry => { + this.telemetrySubscriber = TelemetrySubscriber.createEntityAttributesSubscription( + this.telemetryWsService, this.data.deviceId, LatestTelemetry.LATEST_TELEMETRY, this.zone); + if (!persistToTelemetry) { + const subscriptionCommand = new AttributesSubscriptionCmd(); + subscriptionCommand.entityType = this.data.deviceId.entityType as EntityType; + subscriptionCommand.entityId = this.data.deviceId.id; + subscriptionCommand.scope = AttributeScope.SERVER_SCOPE; + subscriptionCommand.keys = 'active'; + this.telemetrySubscriber.subscriptionCommands.push(subscriptionCommand); + } + + this.telemetrySubscriber.subscribe(); + this.telemetrySubscriber.attributeData$().subscribe( + (data) => { + this.latestTelemetry = data.reduce>((accumulator, item) => { + if (item.key === 'active') { + this.status = item.value; + } else if (item.lastUpdateTs > this.currentTime) { + accumulator.push(item); + } + return accumulator; + }, []); + } + ); + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.html b/ui-ngx/src/app/modules/home/pages/device/device.component.html index 244b1c000b..0b1ef5225c 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.html @@ -46,6 +46,12 @@ [fxShow]="!isEdit"> {{ ((deviceScope === 'customer_user' || deviceScope === 'edge_customer_user') ? 'device.view-credentials' : 'device.manage-credentials') | translate }} + + (click)="close()">{{ closeButtonLabel | translate }}
diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts index 9e1639740e..8a427512aa 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts @@ -42,10 +42,11 @@ import { } from '@shared/models/device.models'; import { UserSettingsService } from '@core/http/user-settings.service'; import { ActionPreferencesUpdateUserSettings } from '@core/auth/auth.actions'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; export interface DeviceCheckConnectivityDialogData { deviceId: EntityId; - showDontShowAgain: boolean; + afterAdd: boolean; } @Component({ selector: 'tb-device-check-connectivity-dialog', @@ -70,7 +71,9 @@ export class DeviceCheckConnectivityDialogComponent extends DeviceTransportType = DeviceTransportType; deviceTransportTypeTranslationMap = deviceTransportTypeTranslationMap; - showDontShowAgain = this.data.showDontShowAgain; + showDontShowAgain: boolean; + dialogTitle: string; + closeButtonLabel: string; notShowAgain = false; @@ -90,6 +93,16 @@ export class DeviceCheckConnectivityDialogComponent extends private userSettingsService: UserSettingsService, private zone: NgZone) { super(store, router, dialogRef); + + if (this.data.afterAdd) { + this.dialogTitle = 'device.connectivity.device-created-check-connectivity'; + this.closeButtonLabel = 'action.skip'; + this.showDontShowAgain = true; + } else { + this.dialogTitle = 'device.connectivity.check-connectivity'; + this.closeButtonLabel = 'action.close'; + this.showDontShowAgain = false; + } } ngOnInit() { @@ -156,7 +169,7 @@ export class DeviceCheckConnectivityDialogComponent extends (data) => { this.latestTelemetry = data.reduce>((accumulator, item) => { if (item.key === 'active') { - this.status = item.value; + this.status = coerceBooleanProperty(item.value); } else if (item.lastUpdateTs > this.currentTime) { accumulator.push(item); } diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index 1c81d3da9b..6682e87551 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -720,7 +720,7 @@ export class DevicesTableConfigResolver implements Resolve Date: Mon, 17 Jul 2023 23:46:06 +0300 Subject: [PATCH 049/166] Removed deleteEntityLatestTimeseries, edited strategies names (#8948) * added rewrite param to delete latest timeseries, enabled single selection deletion * Removed deleteEntityLatestTimeseries, edited strategies names --- ui-ngx/src/app/core/http/attribute.service.ts | 8 ------- .../attribute/attribute-table.component.ts | 21 ++++++++----------- .../delete-timeseries-panel.component.html | 2 +- .../delete-timeseries-panel.component.ts | 8 +++---- .../models/telemetry/telemetry.models.ts | 12 +++++------ .../assets/locale/locale.constant-en_US.json | 8 +++---- 6 files changed, 24 insertions(+), 35 deletions(-) diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index f568758f41..c810a0af22 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -64,14 +64,6 @@ export class AttributeService { return this.http.delete(url, defaultHttpOptionsFromConfig(config)); } - public deleteEntityLatestTimeseries(entityId: EntityId, timeseries: Array, rewrite = true, - config?: RequestConfig): Observable { - const keys = timeseries.map(attribute => encodeURIComponent(attribute.key)).join(','); - let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/latest/delete?keys=${keys}` + - `&rewrite=${rewrite}`; - return this.http.delete(url, defaultHttpOptionsFromConfig(config)); - } - public saveEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array, config?: RequestConfig): Observable { const attributesData: {[key: string]: any} = {}; diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts index 2969feea76..245411f2f9 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts @@ -386,7 +386,7 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI if ($event) { $event.stopPropagation(); } - const isMultipleDeletion = isUndefinedOrNull(attribute) && this.dataSource.selection.selected.length > 1; + const isMultipleDeletion = isUndefinedOrNull(attribute) && this.dataSource.selection.selected.length > 1; const target = $event.target || $event.srcElement || $event.currentTarget; const config = new OverlayConfig(); config.backdropClass = 'cdk-overlay-transparent-backdrop'; @@ -424,34 +424,31 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI componentRef.onDestroy(() => { if (componentRef.instance.result !== null) { const strategy = componentRef.instance.result; - const timeseries = attribute ? [attribute]: this.dataSource.selection.selected; + const deleteTimeseries = attribute ? [attribute]: this.dataSource.selection.selected; let deleteAllDataForKeys = false; let rewriteLatestIfDeleted = false; let startTs = null; let endTs = null; let deleteLatest = true; - let task: Observable; - if (strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY) { + if (strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA) { deleteAllDataForKeys = true; } - if (strategy === TimeseriesDeleteStrategy.DELETE_OLD_DATA_EXCEPT_LATEST_VALUE) { + if (strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_EXCEPT_LATEST_VALUE) { deleteAllDataForKeys = true; deleteLatest = false; } if (strategy === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE) { rewriteLatestIfDeleted = componentRef.instance.rewriteLatestIfDeleted; - task = this.attributeService.deleteEntityLatestTimeseries(this.entityIdValue, timeseries, rewriteLatestIfDeleted); + startTs = deleteTimeseries[0].lastUpdateTs; + endTs = startTs + 1; } - if (strategy === TimeseriesDeleteStrategy.DELETE_DATA_FOR_TIME_PERIOD) { + if (strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD) { startTs = componentRef.instance.startDateTime.getTime(); endTs = componentRef.instance.endDateTime.getTime(); rewriteLatestIfDeleted = componentRef.instance.rewriteLatestIfDeleted; } - if (!task) { - task = this.attributeService.deleteEntityTimeseries(this.entityIdValue, timeseries, deleteAllDataForKeys, - startTs, endTs, rewriteLatestIfDeleted, deleteLatest); - } - task.subscribe(() => this.reloadAttributes()); + this.attributeService.deleteEntityTimeseries(this.entityIdValue, deleteTimeseries, deleteAllDataForKeys, + startTs, endTs, rewriteLatestIfDeleted, deleteLatest).subscribe(() => this.reloadAttributes()); } }); } diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html index 0bd9ac21ef..a49f89a6cc 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html @@ -55,7 +55,7 @@
- {{ "attribute.delete-timeseries.rewrite-latest-value-if-deleted" | translate }} + {{ "attribute.delete-timeseries.rewrite-latest-value" | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts index 2bf6b44859..364ecd2c9a 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts @@ -35,7 +35,7 @@ export interface DeleteTimeseriesPanelData { }) export class DeleteTimeseriesPanelComponent implements OnInit { - strategy: string = TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY; + strategy: string = TimeseriesDeleteStrategy.DELETE_ALL_DATA; result: string = null; @@ -48,8 +48,8 @@ export class DeleteTimeseriesPanelComponent implements OnInit { strategiesTranslationsMap = timeseriesDeleteStrategyTranslations; multipleDeletionStrategies = [ - TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY, - TimeseriesDeleteStrategy.DELETE_OLD_DATA_EXCEPT_LATEST_VALUE + TimeseriesDeleteStrategy.DELETE_ALL_DATA, + TimeseriesDeleteStrategy.DELETE_ALL_DATA_EXCEPT_LATEST_VALUE ]; constructor(@Inject(DELETE_TIMESERIES_PANEL_DATA) public data: DeleteTimeseriesPanelData, @@ -77,7 +77,7 @@ export class DeleteTimeseriesPanelComponent implements OnInit { } isPeriodStrategy(): boolean { - return this.strategy === TimeseriesDeleteStrategy.DELETE_DATA_FOR_TIME_PERIOD; + return this.strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD; } isDeleteLatestStrategy(): boolean { diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 76f6b0f247..d93dde4530 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -62,10 +62,10 @@ export enum TelemetryFeature { } export enum TimeseriesDeleteStrategy { - DELETE_ALL_DATA_INCLUDING_KEY = 'DELETE_ALL_DATA_INCLUDING_KEY', - DELETE_OLD_DATA_EXCEPT_LATEST_VALUE = 'DELETE_OLD_DATA_EXCEPT_LATEST_VALUE', + DELETE_ALL_DATA = 'DELETE_ALL_DATA', + DELETE_ALL_DATA_EXCEPT_LATEST_VALUE = 'DELETE_ALL_DATA_EXCEPT_LATEST_VALUE', DELETE_LATEST_VALUE = 'DELETE_LATEST_VALUE', - DELETE_DATA_FOR_TIME_PERIOD = 'DELETE_DATA_FOR_TIME_PERIOD' + DELETE_ALL_DATA_FOR_TIME_PERIOD = 'DELETE_ALL_DATA_FOR_TIME_PERIOD' } export type TelemetryType = LatestTelemetry | AttributeScope; @@ -98,10 +98,10 @@ export const isClientSideTelemetryType = new Map( export const timeseriesDeleteStrategyTranslations = new Map( [ - [TimeseriesDeleteStrategy.DELETE_ALL_DATA_INCLUDING_KEY, 'attribute.delete-timeseries.all-data-including-key'], - [TimeseriesDeleteStrategy.DELETE_OLD_DATA_EXCEPT_LATEST_VALUE, 'attribute.delete-timeseries.old-data-except-latest'], + [TimeseriesDeleteStrategy.DELETE_ALL_DATA, 'attribute.delete-timeseries.all-data'], + [TimeseriesDeleteStrategy.DELETE_ALL_DATA_EXCEPT_LATEST_VALUE, 'attribute.delete-timeseries.all-data-except-latest-value'], [TimeseriesDeleteStrategy.DELETE_LATEST_VALUE, 'attribute.delete-timeseries.latest-value'], - [TimeseriesDeleteStrategy.DELETE_DATA_FOR_TIME_PERIOD, 'attribute.delete-timeseries.data-for-time-period'] + [TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD, 'attribute.delete-timeseries.all-data-for-time-period'] ] ) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 091ee1e3bf..190a47788c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -723,11 +723,11 @@ "ends-on": "Ends on", "strategy": "Strategy", "delete-strategy": "Delete strategy", - "all-data-including-key": "Delete all data including key", - "old-data-except-latest": "Delete old data except latest value", + "all-data": "Delete all data", + "all-data-except-latest-value": "Delete all data except latest value", "latest-value": "Delete latest value", - "data-for-time-period": "Delete data for time period", - "rewrite-latest-value-if-deleted": "Rewrite latest value if deleted" + "all-data-for-time-period": "Delete all data for time period", + "rewrite-latest-value": "Rewrite latest value" } }, "api-usage": { From c3e775c35389ad9ca7bc9bf2c2b8ebf31e41896f Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 18 Jul 2023 11:38:41 +0300 Subject: [PATCH 050/166] added mqtt server chain certificate --- .../src/main/resources/thingsboard.yml | 13 ++--- .../dao/device/DeviceConnectivityInfo.java | 1 + .../DeviceConnectivityMqttSslCertService.java | 53 +++++++++++++++++++ .../server/dao/device/DeviceServiceImpl.java | 8 +++ .../TbDeviceConnectivitySslCertService.java | 21 ++++++++ .../dao/util/DeviceConnectivityUtil.java | 3 +- 6 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/TbDeviceConnectivitySslCertService.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f11cd15bf8..6eb0a3948c 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -990,27 +990,28 @@ device: connectivity: http: enabled: "${DEVICE_CONNECTIVITY_HTTP_ENABLED:true}" - host: "${DEVICE_CONNECTIVITY_HTTP_HOST:localhost}" + host: "${DEVICE_CONNECTIVITY_HTTP_HOST:}" port: "${DEVICE_CONNECTIVITY_HTTP_PORT:8080}" https: enabled: "${DEVICE_CONNECTIVITY_HTTPS_ENABLED:false}" - host: "${DEVICE_CONNECTIVITY_HTTPS_HOST:localhost}" + host: "${DEVICE_CONNECTIVITY_HTTPS_HOST:}" port: "${DEVICE_CONNECTIVITY_HTTPS_PORT:443}" mqtt: enabled: "${DEVICE_CONNECTIVITY_MQTT_ENABLED:true}" - host: "${DEVICE_CONNECTIVITY_MQTT_HOST:localhost}" + host: "${DEVICE_CONNECTIVITY_MQTT_HOST:}" port: "${DEVICE_CONNECTIVITY_MQTT_PORT:1883}" mqtts: enabled: "${DEVICE_CONNECTIVITY_MQTTS_ENABLED:false}" - host: "${DEVICE_CONNECTIVITY_MQTTS_HOST:localhost}" + host: "${DEVICE_CONNECTIVITY_MQTTS_HOST:}" port: "${DEVICE_CONNECTIVITY_MQTTS_PORT:8883}" + tb_server_chain_path: "${DEVICE_CONNECTIVITY_MQTTS_SERVER_CHAIN_PATH:}" coap: enabled: "${DEVICE_CONNECTIVITY_COAP_ENABLED:true}" - host: "${DEVICE_CONNECTIVITY_COAP_HOST:localhost}" + host: "${DEVICE_CONNECTIVITY_COAP_HOST:}" port: "${DEVICE_CONNECTIVITY_COAP_PORT:5683}" coaps: enabled: "${DEVICE_CONNECTIVITY_COAPS_ENABLED:false}" - host: "${DEVICE_CONNECTIVITY_COAPS_HOST:localhost}" + host: "${DEVICE_CONNECTIVITY_COAPS_HOST:}" port: "${DEVICE_CONNECTIVITY_COAPS_PORT:5684}" # Edges parameters diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java index f570919290..5b169a6e79 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java @@ -22,4 +22,5 @@ public class DeviceConnectivityInfo { private Boolean enabled; private String host; private String port; + private String sslCertPath; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java new file mode 100644 index 0000000000..f6736e918f --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java @@ -0,0 +1,53 @@ +/** + * 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.dao.device; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.ResourceUtils; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; + +@Service +@Slf4j +public class DeviceConnectivityMqttSslCertService implements TbDeviceConnectivitySslCertService { + + private String certificate; + @Autowired + private DeviceConnectivityConfiguration deviceConnectivityConfiguration; + + @PostConstruct + private void postConstruct() throws IOException { + String sslCertPath = deviceConnectivityConfiguration.getConnectivity() + .get(MQTTS) + .getSslCertPath(); + if (!sslCertPath.isEmpty() && ResourceUtils.resourceExists(this, sslCertPath)) { + certificate = FileUtils.readFileToString(new File(sslCertPath), StandardCharsets.UTF_8); + } + } + + @Override + public String getMqttSslCertificate() { + return certificate; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 376133b173..f34c1fa99d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -99,6 +99,7 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.JSON_EXAMPL import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.CHECK_DOCUMENTATION; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.SERVER_CHAIN_PEM; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCoapClientCommand; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCurlCommand; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getMosquittoPublishCommand; @@ -136,6 +137,9 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Tue, 18 Jul 2023 11:51:18 +0300 Subject: [PATCH 051/166] fixed tests --- .../thingsboard/server/controller/DeviceControllerTest.java | 4 ++-- .../dao/device/DeviceConnectivityMqttSslCertService.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 111ca2e6de..12fa4377f6 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -766,7 +766,7 @@ public class DeviceControllerTest extends AbstractControllerTest { credentials.getCredentialsId())); assertThat(commands.get(COAP)).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -v 9 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } @@ -856,7 +856,7 @@ public class DeviceControllerTest extends AbstractControllerTest { assertThat(commands).hasSize(2); assertThat(commands.get(COAP)).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -v 9 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java index f6736e918f..e5851b43c4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java @@ -41,7 +41,7 @@ public class DeviceConnectivityMqttSslCertService implements TbDeviceConnectivit String sslCertPath = deviceConnectivityConfiguration.getConnectivity() .get(MQTTS) .getSslCertPath(); - if (!sslCertPath.isEmpty() && ResourceUtils.resourceExists(this, sslCertPath)) { + if (sslCertPath != null && ResourceUtils.resourceExists(this, sslCertPath)) { certificate = FileUtils.readFileToString(new File(sslCertPath), StandardCharsets.UTF_8); } } From 883eb472f2f022381ae2bf9e9caf84fbd57f3049 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 19 Jul 2023 12:28:42 +0300 Subject: [PATCH 052/166] fixes after merge to PE --- .../server/common/data/EntityType.java | 9 ++++-- .../server/common/data/EntityTypeTest.java | 30 +++++++++++++++++++ .../thingsboard/server/common/msg/TbMsg.java | 6 +--- .../TbCopyAttributesToEntityViewNode.java | 4 +-- .../rule/engine/debug/TbMsgGeneratorNode.java | 16 ++++++---- .../engine/telemetry/TbMsgTimeseriesNode.java | 2 +- 6 files changed, 52 insertions(+), 15 deletions(-) create mode 100644 common/data/src/test/java/org/thingsboard/server/common/data/EntityTypeTest.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index dd53d61f8b..014cc2b521 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -35,7 +35,13 @@ public enum EntityType { ALARM, RULE_CHAIN, RULE_NODE, - ENTITY_VIEW, + ENTITY_VIEW { + // backward compatibility for TbMsgTypeSwitchNode to return correct rule node connection. + @Override + public String getNormalName() { + return "Entity View"; + } + }, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, @@ -53,7 +59,6 @@ public enum EntityType { NOTIFICATION, NOTIFICATION_RULE; - public static final List NORMAL_NAMES = EnumSet.allOf(EntityType.class).stream() .map(EntityType::getNormalName).collect(Collectors.toUnmodifiableList()); diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/EntityTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/EntityTypeTest.java new file mode 100644 index 0000000000..9eee9ec23d --- /dev/null +++ b/common/data/src/test/java/org/thingsboard/server/common/data/EntityTypeTest.java @@ -0,0 +1,30 @@ +/** + * 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; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class EntityTypeTest { + + // backward-compatibility test + @Test + void getNormalNameTest() { + assertThat(EntityType.ENTITY_VIEW.getNormalName()).isEqualTo("Entity View"); + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index c6c1a36694..125260def5 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -438,11 +438,7 @@ public final class TbMsg implements Serializable { public TbMsgCallback getCallback() { // May be null in case of deserialization; - if (callback != null) { - return callback; - } else { - return TbMsgCallback.EMPTY; - } + return Objects.requireNonNullElse(callback, TbMsgCallback.EMPTY); } public void pushToStack(RuleChainId ruleChainId, RuleNodeId ruleNodeId) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 4f69225274..360e81d644 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -96,7 +96,7 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) { if (ATTRIBUTES_DELETED.name().equals(msg.getType())) { List attributes = new ArrayList<>(); - for (JsonElement element : new JsonParser().parse(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray()) { + for (JsonElement element : JsonParser.parseString(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray()) { if (element.isJsonPrimitive()) { JsonPrimitive value = element.getAsJsonPrimitive(); if (value.isString()) { @@ -111,7 +111,7 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { getFutureCallback(ctx, msg, entityView)); } } else { - Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())); + Set attributes = JsonConverter.convertToAttributes(JsonParser.parseString(msg.getData())); List filteredAttributes = attributes.stream().filter(attr -> attributeContainsInEntityView(scope, attr.getKey(), entityView)).collect(Collectors.toList()); ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), entityView.getId(), scope, filteredAttributes, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 2f1aae5000..febb2c1067 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -28,6 +28,7 @@ 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.server.common.data.StringUtils; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.msg.TbMsgType; @@ -96,7 +97,7 @@ public class TbMsgGeneratorNode implements TbNode { if (initialized.compareAndSet(false, true)) { this.scriptEngine = ctx.createScriptEngine(config.getScriptLang(), ScriptLanguage.TBEL.equals(config.getScriptLang()) ? config.getTbelScript() : config.getJsScript(), "prevMsg", "prevMetadata", "prevMsgType"); - scheduleTickMsg(ctx); + scheduleTickMsg(ctx, null); } } else if (initialized.compareAndSet(true, false)) { destroy(); @@ -113,7 +114,7 @@ public class TbMsgGeneratorNode implements TbNode { log.trace("onMsg onSuccess callback, took {}ms, config {}, msg {}", sw.stopAndGetTotalTimeMillis(), config, msg); if (initialized.get() && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { ctx.enqueueForTellNext(m, TbNodeConnectionType.SUCCESS); - scheduleTickMsg(ctx); + scheduleTickMsg(ctx, msg); currentMsgCount++; } }, @@ -121,14 +122,14 @@ public class TbMsgGeneratorNode implements TbNode { log.trace("onMsg onFailure callback, took {}ms, config {}, msg {}", sw.stopAndGetTotalTimeMillis(), config, msg, t); if (initialized.get() && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { ctx.tellFailure(msg, t); - scheduleTickMsg(ctx); + scheduleTickMsg(ctx, msg); currentMsgCount++; } }); } } - private void scheduleTickMsg(TbContext ctx) { + private void scheduleTickMsg(TbContext ctx, TbMsg msg) { log.trace("scheduleTickMsg, config {}", config); long curTs = System.currentTimeMillis(); if (lastScheduledTs == 0L) { @@ -136,7 +137,8 @@ public class TbMsgGeneratorNode implements TbNode { } lastScheduledTs = lastScheduledTs + delay; long curDelay = Math.max(0L, (lastScheduledTs - curTs)); - TbMsg tickMsg = ctx.newMsg(config.getQueueName(), TbMsgType.GENERATOR_NODE_SELF_MSG, ctx.getSelfId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + TbMsg tickMsg = ctx.newMsg(config.getQueueName(), TbMsgType.GENERATOR_NODE_SELF_MSG, ctx.getSelfId(), + getCustomerIdFromMsg(msg), TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); nextTickId = tickMsg.getId(); ctx.tellSelf(tickMsg, curDelay); } @@ -159,6 +161,10 @@ public class TbMsgGeneratorNode implements TbNode { } + private CustomerId getCustomerIdFromMsg(TbMsg msg) { + return msg != null ? msg.getCustomerId() : null; + } + @Override public void destroy() { log.trace("destroy, config {}", config); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java index 706135eaa2..4118d28c22 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java @@ -88,7 +88,7 @@ public class TbMsgTimeseriesNode implements TbNode { } long ts = computeTs(msg, config.isUseServerTs()); String src = msg.getData(); - Map> tsKvMap = JsonConverter.convertToTelemetry(new JsonParser().parse(src), ts); + Map> tsKvMap = JsonConverter.convertToTelemetry(JsonParser.parseString(src), ts); if (tsKvMap.isEmpty()) { ctx.tellFailure(msg, new IllegalArgumentException("Msg body is empty: " + src)); return; From 2cac9aab9d47841e5e0114a48b57c22812aa4b36 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 19 Jul 2023 12:36:36 +0300 Subject: [PATCH 053/166] fix typo --- .../java/org/thingsboard/server/common/data/EntityType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index 014cc2b521..8ca6585718 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -36,7 +36,7 @@ public enum EntityType { RULE_CHAIN, RULE_NODE, ENTITY_VIEW { - // backward compatibility for TbMsgTypeSwitchNode to return correct rule node connection. + // backward compatibility for TbOriginatorTypeSwitchNode to return correct rule node connection. @Override public String getNormalName() { return "Entity View"; From 881861d80dee78e27fd3d16e69e6ea2739c440f7 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 19 Jul 2023 19:44:47 +0200 Subject: [PATCH 054/166] deleted 'remove latest api' --- .../controller/TelemetryController.java | 56 ------------------- .../DefaultTelemetrySubscriptionService.java | 7 --- .../controller/TelemetryControllerTest.java | 44 --------------- .../dao/timeseries/TimeseriesService.java | 2 - .../dao/timeseries/BaseTimeseriesService.java | 33 ++--------- .../api/RuleEngineTelemetryService.java | 2 - 6 files changed, 4 insertions(+), 140 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 449e82fc41..6937fa6c84 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -537,57 +537,6 @@ public class TelemetryController extends BaseController { }); } - @ApiOperation(value = "Delete entity latest time-series data (deleteEntityLatestTimeseries)", - notes = "Delete latest time-series for selected entity based on entity id, entity type and keys. " + - TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = MediaType.APPLICATION_JSON_VALUE) - @ApiResponses(value = { - @ApiResponse(code = 200, message = "Timeseries for the selected keys in the request was removed. " + - "Platform creates an audit log event about entity latest timeseries removal with action type 'TIMESERIES_DELETED'."), - @ApiResponse(code = 400, message = "Platform returns a bad request in case if keys list is empty."), - @ApiResponse(code = 401, message = "User is not authorized to delete entity latest timeseries for selected entity. Most likely, User belongs to different Customer or Tenant."), - @ApiResponse(code = 500, message = "The exception was thrown during processing the request. " + - "Platform creates an audit log event about entity latest timeseries removal with action type 'TIMESERIES_DELETED' that includes an error stacktrace."), - }) - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/{entityType}/{entityId}/timeseries/latest/delete", method = RequestMethod.DELETE) - @ResponseBody - public DeferredResult deleteEntityLatestTimeseries(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") - @PathVariable("entityType") String entityType, - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) - @PathVariable("entityId") String entityIdStr, - @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION, required = true) - @RequestParam(name = "keys") String keysStr, - @ApiParam(value = "If the parameter is set to true, the latest telemetry will be rewritten in case that current latest value was removed, otherwise, in case that parameter is set to false the new latest value will not set.") - @RequestParam(name = "rewrite", defaultValue = "false") boolean rewrite) throws ThingsboardException { - EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); - return deleteLatestTimeseries(entityId, keysStr, rewrite); - } - - private DeferredResult deleteLatestTimeseries(EntityId entityIdStr, String keysStr, boolean rewrite) throws ThingsboardException { - List keys = toKeysList(keysStr); - if (keys.isEmpty()) { - return getImmediateDeferredResult("Empty keys: " + keysStr, HttpStatus.BAD_REQUEST); - } - SecurityUser user = getCurrentUser(); - - return accessValidator.validateEntityAndCallback(user, Operation.WRITE_TELEMETRY, entityIdStr, (result, tenantId, entityId) -> - tsSubService.deleteLatestAndNotify(tenantId, entityId, keys, rewrite, new FutureCallback<>() { - @Override - public void onSuccess(@Nullable Void tmp) { - logLatestTimeseriesDeleted(user, entityId, keys, null); - result.setResult(new ResponseEntity<>(HttpStatus.OK)); - } - - @Override - public void onFailure(Throwable t) { - logLatestTimeseriesDeleted(user, entityId, keys, t); - result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); - } - }) - ); - } - @ApiOperation(value = "Delete device attributes (deleteDeviceAttributes)", notes = "Delete device attributes using provided Device Id, scope and a list of keys. " + "Referencing a non-existing Device Id will cause an error" + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, @@ -880,11 +829,6 @@ public class TelemetryController extends BaseController { toException(e), keys, startTs, endTs); } - private void logLatestTimeseriesDeleted(SecurityUser user, EntityId entityId, List keys, Throwable e) { - notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.TIMESERIES_DELETED, user, - toException(e), keys); - } - private void logTelemetryUpdated(SecurityUser user, EntityId entityId, List telemetry, Throwable e) { notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.TIMESERIES_UPDATED, user, toException(e), telemetry); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index a97b7f386d..3f5e52796a 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -316,13 +316,6 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer addWsCallback(deleteFuture, list -> onTimeSeriesDelete(tenantId, entityId, keys, list)); } - @Override - public void deleteLatestAndNotify(TenantId tenantId, EntityId entityId, List keys, boolean rewrite, FutureCallback callback) { - ListenableFuture> deleteFuture = tsService.removeLatest(tenantId, entityId, keys, rewrite); - addVoidCallback(deleteFuture, callback); - addWsCallback(deleteFuture, list -> onTimeSeriesDelete(tenantId, entityId, keys, list)); - } - @Override public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback callback) { saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value) diff --git a/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java index 2fdab098e4..d97937c684 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java @@ -46,50 +46,6 @@ public class TelemetryControllerTest extends AbstractControllerTest { doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", invalidRequestBody, String.class, status().isBadRequest()); } - @Test - public void testDeleteLatest() throws Exception { - loginTenantAdmin(); - Device device = createDevice(); - - SingleEntityFilter filter = new SingleEntityFilter(); - filter.setSingleEntity(device.getId()); - - getWsClient().subscribeLatestUpdate(List.of(new EntityKey(TIME_SERIES, "data")), filter); - - getWsClient().registerWaitForUpdate(1); - - long startTs = System.currentTimeMillis(); - - String testBody = "{\"data\": \"value\"}"; - doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", testBody, String.class, status().isOk()); - - long endTs = System.currentTimeMillis(); - - ObjectNode latest = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data", ObjectNode.class); - - Assert.assertNotNull(latest); - var data = latest.get("data"); - Assert.assertNotNull(data); - - Assert.assertEquals("value", data.get(0).get("value").asText()); - - ObjectNode timeseries = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data&startTs={startTs}&endTs={endTs}", ObjectNode.class, startTs, endTs); - - Assert.assertNotNull(timeseries); - - Assert.assertEquals("value", timeseries.get("data").get(0).get("value").asText()); - - doDeleteAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/latest/delete?keys=data", String.class); - - latest = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data", ObjectNode.class); - - Assert.assertTrue(latest.get("data").get(0).get("value").isNull()); - - timeseries = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/timeseries?keys=data&startTs={startTs}&endTs={endTs}", ObjectNode.class, startTs, endTs); - - Assert.assertEquals("value", timeseries.get("data").get(0).get("value").asText()); - } - @Test public void testDeleteAllTelemetryWithLatest() throws Exception { loginTenantAdmin(); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java index 06e42e09e7..c2bc997235 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java @@ -58,8 +58,6 @@ public interface TimeseriesService { ListenableFuture> removeLatest(TenantId tenantId, EntityId entityId, Collection keys); - ListenableFuture> removeLatest(TenantId tenantId, EntityId entityId, Collection keys, boolean rewrite); - ListenableFuture> removeAllLatest(TenantId tenantId, EntityId entityId); List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index d101e63a65..6b8bfa9d64 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -46,7 +46,6 @@ import org.thingsboard.server.dao.service.Validator; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -252,37 +251,13 @@ public class BaseTimeseriesService implements TimeseriesService { @Override public ListenableFuture> removeLatest(TenantId tenantId, EntityId entityId, Collection keys) { - return removeLatest(tenantId, entityId, keys, false); - } - - @Override - public ListenableFuture> removeLatest(TenantId tenantId, EntityId entityId, Collection keys, boolean rewrite) { validate(entityId); List> futures = Lists.newArrayListWithExpectedSize(keys.size()); - - ListenableFuture> latestFuture; - - if (rewrite) { - latestFuture = findLatest(tenantId, entityId, keys); - } else { - latestFuture = Futures.immediateFuture(null); + for (String key : keys) { + DeleteTsKvQuery query = new BaseDeleteTsKvQuery(key, 0, System.currentTimeMillis(), false); + futures.add(timeseriesLatestDao.removeLatest(tenantId, entityId, query)); } - - return Futures.transformAsync(latestFuture, latest -> { - Map keyTsMap; - if (latest != null) { - keyTsMap = latest.stream().collect(Collectors.toMap(TsKvEntry::getKey, TsKvEntry::getTs)); - } else { - keyTsMap = Collections.emptyMap(); - } - - for (String key : keys) { - long startTs = keyTsMap.getOrDefault(key, 0L); - DeleteTsKvQuery query = new BaseDeleteTsKvQuery(key, startTs, System.currentTimeMillis(), rewrite); - futures.add(timeseriesLatestDao.removeLatest(tenantId, entityId, query)); - } - return Futures.allAsList(futures); - }, MoreExecutors.directExecutor()); + return Futures.allAsList(futures); } @Override diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java index 795eaeb785..a61e83f48f 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java @@ -71,6 +71,4 @@ public interface RuleEngineTelemetryService { void deleteAllLatest(TenantId tenantId, EntityId entityId, FutureCallback> callback); void deleteTimeseriesAndNotify(TenantId tenantId, EntityId entityId, List keys, List deleteTsKvQueries, FutureCallback callback); - - void deleteLatestAndNotify(TenantId tenantId, EntityId entityId, List keys, boolean rewrite, FutureCallback callback); } From db46b7988da7cce2f75d4d1e4c18372f6c2cb3e7 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 20 Jul 2023 12:33:15 +0300 Subject: [PATCH 055/166] refactored code to take into account operating system --- .../server/controller/BaseController.java | 9 + .../controller/ControllerConstants.java | 2 + .../DeviceConnectivityController.java | 108 +++++ .../server/controller/DeviceController.java | 33 -- .../src/main/resources/thingsboard.yml | 2 +- .../DeviceConnectivityControllerTest.java | 398 ++++++++++++++++++ .../controller/DeviceControllerTest.java | 184 +------- .../dao/device/DeviceConnectivityService.java | 13 +- .../server/dao/device/DeviceService.java | 3 - .../dao/device/DeviceConnectivityInfo.java | 2 +- .../DeviceConnectivityMqttSslCertService.java | 53 --- .../server/dao/device/DeviceServiceImpl.java | 109 ----- .../DeviceСonnectivityServiceImpl.java | 224 ++++++++++ .../dao/util/DeviceConnectivityUtil.java | 51 ++- 14 files changed, 802 insertions(+), 389 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java rename dao/src/main/java/org/thingsboard/server/dao/device/TbDeviceConnectivitySslCertService.java => common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java (62%) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 68a987a0bc..a03fcd36a4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -113,6 +113,7 @@ import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.ClaimDevicesService; +import org.thingsboard.server.dao.device.DeviceConnectivityService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; @@ -163,6 +164,7 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.mail.MessagingException; import javax.servlet.http.HttpServletResponse; import javax.validation.ConstraintViolation; +import java.io.IOException; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -208,6 +210,9 @@ public abstract class BaseController { @Autowired protected DeviceService deviceService; + @Autowired + protected DeviceConnectivityService deviceConnectivityService; + @Autowired protected DeviceProfileService deviceProfileService; @@ -755,6 +760,10 @@ public abstract class BaseController { return checkEntityId(resourceId, resourceService::findResourceInfoById, operation); } + String checkSslServerPemFile(String protocol) throws ThingsboardException, IOException { + return checkNotNull(deviceConnectivityService.getSslServerChain(protocol), "Mqtt ssl server chain pem file is not found"); + } + OtaPackage checkOtaPackageId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException { return checkEntityId(otaPackageId, otaPackageService::findOtaPackageById, operation); } diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index a6a49f6b3c..f31cebd258 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -24,6 +24,7 @@ public class ControllerConstants { protected static final String CUSTOMER_ID = "customerId"; protected static final String TENANT_ID = "tenantId"; protected static final String DEVICE_ID = "deviceId"; + protected static final String PROTOCOL = "protocol"; protected static final String EDGE_ID = "edgeId"; protected static final String RPC_ID = "rpcId"; protected static final String ENTITY_ID = "entityId"; @@ -34,6 +35,7 @@ public class ControllerConstants { protected static final String DASHBOARD_ID_PARAM_DESCRIPTION = "A string value representing the dashboard id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String RPC_ID_PARAM_DESCRIPTION = "A string value representing the rpc id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String DEVICE_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; + protected static final String PROTOCOL_PARAM_DESCRIPTION = "A string value representing the device connectivity protocol. Possible values: 'mqtt', 'mqtts', 'http', 'https', 'coap', 'coaps'"; protected static final String ENTITY_VIEW_ID_PARAM_DESCRIPTION = "A string value representing the entity view id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String DEVICE_PROFILE_ID_PARAM_DESCRIPTION = "A string value representing the device profile id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java new file mode 100644 index 0000000000..bf745a2033 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java @@ -0,0 +1,108 @@ +/** + * 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.controller; + +import com.fasterxml.jackson.databind.JsonNode; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.system.SystemSecurityService; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Map; + +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID; +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PROTOCOL; +import static org.thingsboard.server.controller.ControllerConstants.PROTOCOL_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT_SSL_PEM_FILE_NAME; + +@RestController +@TbCoreComponent +@RequestMapping("/api") +@RequiredArgsConstructor +@Slf4j +public class DeviceConnectivityController extends BaseController { + + private final SystemSecurityService systemSecurityService; + + @ApiOperation(value = "Get commands to publish device telemetry (getDevicePublishTelemetryCommands)", + notes = "Fetch the list of commands to publish device telemetry based on device profile " + + "If the user has the authority of 'Tenant Administrator', the server checks that the device is owned by the same tenant. " + + "If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer. " + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK", + examples = @io.swagger.annotations.Example( + value = { + @io.swagger.annotations.ExampleProperty( + mediaType="application/json", + value="{\"http\":\"curl -v -X POST http://localhost:8080/api/v1/0ySs4FTOn5WU15XLmal8/telemetry --header Content-Type:application/json --data {temperature:25}\"," + + "\"mqtt\":\"mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -i myClient1 -u myUsername1 -P myPassword -m {temperature:25}\"," + + "\"coap\":\"coap-client -m POST coap://localhost:5683/api/v1/0ySs4FTOn5WU15XLmal8/telemetry -t json -e {temperature:25}\"}")}))}) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/device-connectivity/{deviceId}", method = RequestMethod.GET) + @ResponseBody + public JsonNode getDevicePublishTelemetryCommands(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) + @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException { + checkParameter(DEVICE_ID, strDeviceId); + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS); + + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); + return deviceConnectivityService.findDevicePublishTelemetryCommands(baseUrl, device); + } + + @ApiOperation(value = "Download mqtt ssl certificate using file path defined in device.connectivity properties (downloadMqttServerCertificate)", notes = "Download Mqtt server certificate." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/device-connectivity/{protocol}/certificate/download", method = RequestMethod.GET) + @ResponseBody + public ResponseEntity downloadMqttServerCertificate(@ApiParam(value = PROTOCOL_PARAM_DESCRIPTION) + @PathVariable(PROTOCOL) String protocol) throws ThingsboardException, IOException { + String certificate = checkSslServerPemFile(protocol); + + ByteArrayResource cert = new ByteArrayResource(certificate.getBytes()); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + MQTT_SSL_PEM_FILE_NAME) + .header("x-filename", MQTT_SSL_PEM_FILE_NAME) + .contentLength(cert.contentLength()) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(cert); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index e080574d36..07adb1ef1c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -21,11 +21,8 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -79,12 +76,9 @@ import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import java.net.URISyntaxException; import javax.validation.Valid; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -173,33 +167,6 @@ public class DeviceController extends BaseController { return checkDeviceInfoId(deviceId, Operation.READ); } - @ApiOperation(value = "Get commands to publish device telemetry (getDevicePublishTelemetryCommands)", - notes = "Fetch the list of commands to publish device telemetry based on device profile " + - "If the user has the authority of 'Tenant Administrator', the server checks that the device is owned by the same tenant. " + - "If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer. " + - TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", - examples = @io.swagger.annotations.Example( - value = { - @io.swagger.annotations.ExampleProperty( - mediaType="application/json", - value="{\"http\":\"curl -v -X POST http://localhost:8080/api/v1/0ySs4FTOn5WU15XLmal8/telemetry --header Content-Type:application/json --data {temperature:25}\"," + - "\"mqtt\":\"mosquitto_pub -d -q 1 -h localhost -t v1/devices/me/telemetry -i myClient1 -u myUsername1 -P myPassword -m {temperature:25}\"," + - "\"coap\":\"coap-client -m POST coap://localhost:5683/api/v1/0ySs4FTOn5WU15XLmal8/telemetry -t json -e {temperature:25}\"}")}))}) - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/device/{deviceId}/commands", method = RequestMethod.GET) - @ResponseBody - public Map getDevicePublishTelemetryCommands(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) - @PathVariable(DEVICE_ID) String strDeviceId, HttpServletRequest request) throws ThingsboardException, URISyntaxException { - checkParameter(DEVICE_ID, strDeviceId); - DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS); - - String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); - return deviceService.findDevicePublishTelemetryCommands(baseUrl, device); - } - @ApiOperation(value = "Create Or Update Device (saveDevice)", notes = "Create or update the Device. When creating device, platform generates Device Id as " + UUID_WIKI_LINK + "Device credentials are also generated if not provided in the 'accessToken' request parameter. " + diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 6eb0a3948c..5886e74ce4 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1004,7 +1004,7 @@ device: enabled: "${DEVICE_CONNECTIVITY_MQTTS_ENABLED:false}" host: "${DEVICE_CONNECTIVITY_MQTTS_HOST:}" port: "${DEVICE_CONNECTIVITY_MQTTS_PORT:8883}" - tb_server_chain_path: "${DEVICE_CONNECTIVITY_MQTTS_SERVER_CHAIN_PATH:}" + ssl_server_pem_path: "${DEVICE_CONNECTIVITY_MQTTS_SERVER_CHAIN_PATH:}" coap: enabled: "${DEVICE_CONNECTIVITY_COAP_ENABLED:true}" host: "${DEVICE_CONNECTIVITY_COAP_HOST:}" diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java new file mode 100644 index 0000000000..8e27857878 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -0,0 +1,398 @@ +/** + * 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.controller; + +import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.AdditionalAnswers; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ThingsBoardExecutors; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; +import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceCredentialsId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; +import org.thingsboard.server.dao.device.DeviceDao; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; +import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAP; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.DOCKER; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.LINUX; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; +import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.WINDOWS; + +@TestPropertySource(properties = { + "device.connectivity.https.enabled=true", + "device.connectivity.mqtts.enabled=true", + "device.connectivity.coaps.enabled=true", +}) +@ContextConfiguration(classes = {DeviceConnectivityControllerTest.Config.class}) +@DaoSqlTest +public class DeviceConnectivityControllerTest extends AbstractControllerTest { + static final TypeReference> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() { + }; + + private static final String DEVICE_TELEMETRY_TOPIC = "v1/devices/customTopic"; + private static final String CHECK_DOCUMENTATION = "Check documentation"; + + ListeningExecutorService executor; + + private Tenant savedTenant; + private User tenantAdmin; + private DeviceProfileId mqttDeviceProfileId; + private DeviceProfileId coapDeviceProfileId; + + static class Config { + @Bean + @Primary + public DeviceDao deviceDao(DeviceDao deviceDao) { + return Mockito.mock(DeviceDao.class, AdditionalAnswers.delegatesTo(deviceDao)); + } + } + + @Before + public void beforeTest() throws Exception { + executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); + + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + DeviceProfile mqttProfile = new DeviceProfile(); + mqttProfile.setName("Mqtt device profile"); + mqttProfile.setType(DeviceProfileType.DEFAULT); + mqttProfile.setTransportType(DeviceTransportType.MQTT); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); + MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration(); + transportConfiguration.setDeviceTelemetryTopic(DEVICE_TELEMETRY_TOPIC); + deviceProfileData.setTransportConfiguration(transportConfiguration); + mqttProfile.setProfileData(deviceProfileData); + mqttProfile.setDefault(false); + mqttProfile.setDefaultRuleChainId(null); + + mqttDeviceProfileId = doPost("/api/deviceProfile", mqttProfile, DeviceProfile.class).getId(); + + DeviceProfile coapProfile = new DeviceProfile(); + coapProfile.setName("Coap device profile"); + coapProfile.setType(DeviceProfileType.DEFAULT); + coapProfile.setTransportType(DeviceTransportType.COAP); + DeviceProfileData deviceProfileData2 = new DeviceProfileData(); + deviceProfileData2.setConfiguration(new DefaultDeviceProfileConfiguration()); + deviceProfileData2.setTransportConfiguration(new CoapDeviceProfileTransportConfiguration()); + coapProfile.setProfileData(deviceProfileData); + coapProfile.setDefault(false); + coapProfile.setDefaultRuleChainId(null); + + coapDeviceProfileId = doPost("/api/deviceProfile", coapProfile, DeviceProfile.class).getId(); + } + + @After + public void afterTest() throws Exception { + executor.shutdownNow(); + + loginSysAdmin(); + + doDelete("/api/tenant/" + savedTenant.getId().getId()) + .andExpect(status().isOk()); + } + + @Test + public void testFetchPublishTelemetryCommandsForDefaultDevice() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setType("default"); + Device savedDevice = doPost("/api/device", device, Device.class); + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + assertThat(commands).hasSize(3); + JsonNode httpCommands = commands.get(HTTP); + assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://localhost:8080/api/v1/%s/telemetry " + + "--header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost:443/api/v1/%s/telemetry " + + "--header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + + + JsonNode linuxMqttCommands = commands.get(MQTT).get(LINUX); + assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + + "-u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(linuxMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + + "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + + JsonNode windowsMqttCommands = commands.get(MQTT).get(WINDOWS); + assertThat(windowsMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + + "-u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + + + JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + + " -p 1883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --volume pathToFile/tb-server-chain.pem:/tmp/tb-server-chain.pem " + + "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + credentials.getCredentialsId())); + + JsonNode linuxCoapCommands = commands.get(COAP).get(LINUX); + assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry " + + "-t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry" + + " -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + } + + @Test + public void testFetchPublishTelemetryCommandsForMqttDeviceWithAccessToken() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(mqttDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); + assertThat(commands).hasSize(1); + + JsonNode linuxMqttCommands = commands.get(MQTT).get(LINUX); + assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + + "-u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + assertThat(linuxMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + + "-t %s -u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + + JsonNode windowsMqttCommands = commands.get(MQTT).get(WINDOWS); + assertThat(windowsMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + + "-u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + + + JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + + " -p 1883 -t %s -u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --volume pathToFile/tb-server-chain.pem:/tmp/tb-server-chain.pem " + + "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + } + + @Test + public void testFetchPublishTelemetryCommandsForDeviceWithMqttBasicCreds() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(mqttDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + credentials.setCredentialsId(null); + credentials.setCredentialsType(DeviceCredentialsType.MQTT_BASIC); + BasicMqttCredentials basicMqttCredentials = new BasicMqttCredentials(); + String clientId = "testClientId"; + String userName = "testUsername"; + String password = "testPassword"; + basicMqttCredentials.setClientId(clientId); + basicMqttCredentials.setUserName(userName); + basicMqttCredentials.setPassword(password); + credentials.setCredentialsValue(JacksonUtil.toString(basicMqttCredentials)); + doPost("/api/device/credentials", credentials) + .andExpect(status().isOk()); + + + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); + assertThat(commands).hasSize(1); + + JsonNode linuxMqttCommands = commands.get(MQTT).get(LINUX); + assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + + "-i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + assertThat(linuxMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + + "-t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + + JsonNode windowsMqttCommands = commands.get(MQTT).get(WINDOWS); + assertThat(windowsMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + + "-i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + + + JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + + " -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --volume pathToFile/tb-server-chain.pem:/tmp/tb-server-chain.pem " + + "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + } + + @Test + public void testFetchPublishTelemetryCommandsForDeviceWithX509Creds() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(mqttDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + credentials.setCredentialsId(null); + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + credentials.setCredentialsValue("testValue"); + doPost("/api/device/credentials", credentials) + .andExpect(status().isOk()); + + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + assertThat(commands).hasSize(1); + assertThat(commands.get(MQTT).get(LINUX).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(MQTT).get(WINDOWS).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(MQTT).get(DOCKER).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); + } + + @Test + public void testFetchPublishTelemetryCommandsForСoapDevice() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(coapDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + assertThat(commands).hasSize(1); + + JsonNode linuxCommands = commands.get(COAP).get(LINUX); + assertThat(linuxCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(linuxCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + credentials.getCredentialsId())); + } + + @Test + public void testFetchPublishTelemetryCommandsForСoapDeviceWithX509Creds() throws Exception { + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(coapDeviceProfileId); + + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + credentials.setCredentialsId(null); + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + credentials.setCredentialsValue("testValue"); + doPost("/api/device/credentials", credentials) + .andExpect(status().isOk()); + + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + assertThat(commands).hasSize(1); + assertThat(commands.get(COAP).get(LINUX).get(COAPS).asText()).isEqualTo(CHECK_DOCUMENTATION); + } +} diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 12fa4377f6..287e383317 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -93,27 +93,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAP; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; - -@TestPropertySource(properties = { - "device.connectivity.https.enabled=true", - "device.connectivity.mqtts.enabled=true", - "device.connectivity.coaps.enabled=true", -}) + @ContextConfiguration(classes = {DeviceControllerTest.Config.class}) @DaoSqlTest public class DeviceControllerTest extends AbstractControllerTest { static final TypeReference> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() { }; - private static final String DEVICE_TELEMETRY_TOPIC = "v1/devices/customTopic"; - private static final String CHECK_DOCUMENTATION = "Check documentation"; - ListeningExecutorService executor; List> futures; @@ -121,8 +107,6 @@ public class DeviceControllerTest extends AbstractControllerTest { private Tenant savedTenant; private User tenantAdmin; - private DeviceProfileId mqttDeviceProfileId; - private DeviceProfileId coapDeviceProfileId; @SpyBean private GatewayNotificationsService gatewayNotificationsService; @@ -157,34 +141,6 @@ public class DeviceControllerTest extends AbstractControllerTest { tenantAdmin.setLastName("Downs"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); - - DeviceProfile mqttProfile = new DeviceProfile(); - mqttProfile.setName("Mqtt device profile"); - mqttProfile.setType(DeviceProfileType.DEFAULT); - mqttProfile.setTransportType(DeviceTransportType.MQTT); - DeviceProfileData deviceProfileData = new DeviceProfileData(); - deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); - MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration(); - transportConfiguration.setDeviceTelemetryTopic(DEVICE_TELEMETRY_TOPIC); - deviceProfileData.setTransportConfiguration(transportConfiguration); - mqttProfile.setProfileData(deviceProfileData); - mqttProfile.setDefault(false); - mqttProfile.setDefaultRuleChainId(null); - - mqttDeviceProfileId = doPost("/api/deviceProfile", mqttProfile, DeviceProfile.class).getId(); - - DeviceProfile coapProfile = new DeviceProfile(); - coapProfile.setName("Coap device profile"); - coapProfile.setType(DeviceProfileType.DEFAULT); - coapProfile.setTransportType(DeviceTransportType.COAP); - DeviceProfileData deviceProfileData2 = new DeviceProfileData(); - deviceProfileData2.setConfiguration(new DefaultDeviceProfileConfiguration()); - deviceProfileData2.setTransportConfiguration(new CoapDeviceProfileTransportConfiguration()); - coapProfile.setProfileData(deviceProfileData); - coapProfile.setDefault(false); - coapProfile.setDefaultRuleChainId(null); - - coapDeviceProfileId = doPost("/api/deviceProfile", coapProfile, DeviceProfile.class).getId(); } @After @@ -743,144 +699,6 @@ public class DeviceControllerTest extends AbstractControllerTest { Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); } - @Test - public void testFetchPublishTelemetryCommandsForDefaultDevice() throws Exception { - Device device = new Device(); - device.setName("My device"); - device.setType("default"); - Device savedDevice = doPost("/api/device", device, Device.class); - Map commands = - doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); - - DeviceCredentials credentials = - doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - - assertThat(commands).hasSize(6); - assertThat(commands.get(HTTP)).isEqualTo(String.format("curl -v -X POST http://localhost:8080/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", - credentials.getCredentialsId())); - assertThat(commands.get(HTTPS)).isEqualTo(String.format("curl -v -X POST https://localhost:443/api/v1/%s/telemetry --header Content-Type:application/json --data \"{temperature:25}\"", - credentials.getCredentialsId())); - assertThat(commands.get(MQTT)).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", - credentials.getCredentialsId())); - assertThat(commands.get(MQTTS)).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", - credentials.getCredentialsId())); - assertThat(commands.get(COAP)).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", - credentials.getCredentialsId())); - assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", - credentials.getCredentialsId())); - } - - @Test - public void testFetchPublishTelemetryCommandsForMqttDeviceWithAccessToken() throws Exception { - Device device = new Device(); - device.setName("My device"); - device.setDeviceProfileId(mqttDeviceProfileId); - - Device savedDevice = doPost("/api/device", device, Device.class); - DeviceCredentials credentials = - doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - - Map commands = - doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); - assertThat(commands).hasSize(2); - assertThat(commands.get(MQTT)).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -u %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - assertThat(commands.get(MQTTS)).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - } - - @Test - public void testFetchPublishTelemetryCommandsForDeviceWithMqttBasicCreds() throws Exception { - Device device = new Device(); - device.setName("My device"); - device.setDeviceProfileId(mqttDeviceProfileId); - - Device savedDevice = doPost("/api/device", device, Device.class); - DeviceCredentials credentials = - doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - credentials.setCredentialsId(null); - credentials.setCredentialsType(DeviceCredentialsType.MQTT_BASIC); - BasicMqttCredentials basicMqttCredentials = new BasicMqttCredentials(); - String clientId = "testClientId"; - String userName = "testUsername"; - String password = "testPassword"; - basicMqttCredentials.setClientId(clientId); - basicMqttCredentials.setUserName(userName); - basicMqttCredentials.setPassword(password); - credentials.setCredentialsValue(JacksonUtil.toString(basicMqttCredentials)); - doPost("/api/device/credentials", credentials) - .andExpect(status().isOk()); - - Map commands = - doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); - assertThat(commands).hasSize(2); - assertThat(commands.get(MQTT)).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - assertThat(commands.get(MQTTS)).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - } - - @Test - public void testFetchPublishTelemetryCommandsForDeviceWithX509Creds() throws Exception { - Device device = new Device(); - device.setName("My device"); - device.setDeviceProfileId(mqttDeviceProfileId); - - Device savedDevice = doPost("/api/device", device, Device.class); - DeviceCredentials credentials = - doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - credentials.setCredentialsId(null); - credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); - credentials.setCredentialsValue("testValue"); - doPost("/api/device/credentials", credentials) - .andExpect(status().isOk()); - - Map commands = - doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); - assertThat(commands).hasSize(1); - assertThat(commands.get(MQTTS)).isEqualTo(CHECK_DOCUMENTATION); - } - - @Test - public void testFetchPublishTelemetryCommandsForСoapDevice() throws Exception { - Device device = new Device(); - device.setName("My device"); - device.setDeviceProfileId(coapDeviceProfileId); - - Device savedDevice = doPost("/api/device", device, Device.class); - DeviceCredentials credentials = - doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - - Map commands = - doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); - assertThat(commands).hasSize(2); - assertThat(commands.get(COAP)).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", - credentials.getCredentialsId())); - assertThat(commands.get(COAPS)).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", - credentials.getCredentialsId())); - } - - @Test - public void testFetchPublishTelemetryCommandsForСoapDeviceWithX509Creds() throws Exception { - Device device = new Device(); - device.setName("My device"); - device.setDeviceProfileId(coapDeviceProfileId); - - Device savedDevice = doPost("/api/device", device, Device.class); - DeviceCredentials credentials = - doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - credentials.setCredentialsId(null); - credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); - credentials.setCredentialsValue("testValue"); - doPost("/api/device/credentials", credentials) - .andExpect(status().isOk()); - - Map commands = - doGetTyped("/api/device/" + savedDevice.getId().getId() + "/commands", new TypeReference<>() {}); - assertThat(commands).hasSize(1); - assertThat(commands.get(COAPS)).isEqualTo(CHECK_DOCUMENTATION); - } - @Test public void testSaveDeviceCredentials() throws Exception { Device device = new Device(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/TbDeviceConnectivitySslCertService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java similarity index 62% rename from dao/src/main/java/org/thingsboard/server/dao/device/TbDeviceConnectivitySslCertService.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java index 43b7f39d30..83f35d5566 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/TbDeviceConnectivitySslCertService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java @@ -15,7 +15,16 @@ */ package org.thingsboard.server.dao.device; +import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.server.common.data.Device; -public interface TbDeviceConnectivitySslCertService { - String getMqttSslCertificate(); +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Map; + +public interface DeviceConnectivityService { + + JsonNode findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException; + + String getSslServerChain(String protocol) throws IOException; } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index a029f27309..510250d264 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.device; -import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; @@ -46,8 +45,6 @@ public interface DeviceService extends EntityDaoService { DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId); - Map findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException; - Device findDeviceById(TenantId tenantId, DeviceId deviceId); ListenableFuture findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java index 5b169a6e79..fa5c61328b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java @@ -22,5 +22,5 @@ public class DeviceConnectivityInfo { private Boolean enabled; private String host; private String port; - private String sslCertPath; + private String sslServerPemPath; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java deleted file mode 100644 index e5851b43c4..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityMqttSslCertService.java +++ /dev/null @@ -1,53 +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.dao.device; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.FileUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.ResourceUtils; - -import javax.annotation.PostConstruct; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; - -@Service -@Slf4j -public class DeviceConnectivityMqttSslCertService implements TbDeviceConnectivitySslCertService { - - private String certificate; - @Autowired - private DeviceConnectivityConfiguration deviceConnectivityConfiguration; - - @PostConstruct - private void postConstruct() throws IOException { - String sslCertPath = deviceConnectivityConfiguration.getConnectivity() - .get(MQTTS) - .getSslCertPath(); - if (sslCertPath != null && ResourceUtils.resourceExists(this, sslCertPath)) { - certificate = FileUtils.readFileToString(new File(sslCertPath), StandardCharsets.UTF_8); - } - } - - @Override - public String getMqttSslCertificate() { - return certificate; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index f34c1fa99d..5a6caefd58 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -38,7 +38,6 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration; @@ -48,7 +47,6 @@ import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; @@ -76,13 +74,9 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; -import java.net.URI; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -91,18 +85,6 @@ import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateIds; import static org.thingsboard.server.dao.service.Validator.validatePageLink; import static org.thingsboard.server.dao.service.Validator.validateString; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAP; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.JSON_EXAMPLE_PAYLOAD; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.CHECK_DOCUMENTATION; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.SERVER_CHAIN_PEM; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCoapClientCommand; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCurlCommand; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getMosquittoPublishCommand; @Service("DeviceDaoService") @Slf4j @@ -134,12 +116,6 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException { - DeviceId deviceId = device.getId(); - log.trace("Executing findDevicePublishTelemetryCommands [{}]", deviceId); - validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); - - String defaultHostname = new URI(baseUrl).getHost(); - DeviceCredentials creds = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); - DeviceTransportType transportType = deviceProfile.getTransportType(); - - Map commands = new HashMap<>(); - switch (transportType) { - case DEFAULT: - Optional.ofNullable(getHttpPublishCommand(HTTP, defaultHostname, creds)).ifPresent(v -> commands.put(HTTP, v)); - Optional.ofNullable(getHttpPublishCommand(HTTPS, defaultHostname, creds)).ifPresent(v -> commands.put(HTTPS, v)); - Optional.ofNullable(getMqttPublishCommand(MQTT, defaultHostname, creds)).ifPresent(v -> commands.put(MQTT, v)); - Optional.ofNullable(getMqttPublishCommand(MQTTS, defaultHostname, creds)).ifPresent(v -> commands.put(MQTTS, v)); - Optional.ofNullable(getCoapPublishCommand(COAP, defaultHostname, creds)).ifPresent(v -> commands.put(COAP, v)); - Optional.ofNullable(getCoapPublishCommand(COAPS, defaultHostname, creds)).ifPresent(v -> commands.put(COAPS, v)); - break; - case MQTT: - MqttDeviceProfileTransportConfiguration transportConfiguration = - (MqttDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); - String topicName = transportConfiguration.getDeviceTelemetryTopic(); - TransportPayloadType payloadType = transportConfiguration.getTransportPayloadTypeConfiguration().getTransportPayloadType(); - String payload = (payloadType == TransportPayloadType.PROTOBUF) ? " -f protobufFileName" : " -m " + JSON_EXAMPLE_PAYLOAD; - - Optional.ofNullable(getMqttPublishCommand(MQTT, defaultHostname, topicName, creds, payload)).ifPresent(v -> commands.put(MQTT, v)); - Optional.ofNullable(getMqttPublishCommand(MQTTS, defaultHostname, topicName, creds, payload)).ifPresent(v -> commands.put(MQTTS, v)); - break; - case COAP: - Optional.ofNullable(getCoapPublishCommand(COAP, defaultHostname, creds)).ifPresent(v -> commands.put(COAP, v)); - Optional.ofNullable(getCoapPublishCommand(COAPS, defaultHostname, creds)).ifPresent(v -> commands.put(COAPS, v)); - break; - default: - commands.put(transportType.name(), CHECK_DOCUMENTATION); - } - - if (commands.containsKey(MQTTS) && deviceConnectivityMqttSslCertService.getMqttSslCertificate() != null) { - commands.put(SERVER_CHAIN_PEM, deviceConnectivityMqttSslCertService.getMqttSslCertificate()); - } - return commands; - } - @Override public Device findDeviceById(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceById [{}]", deviceId); @@ -747,44 +678,4 @@ public class DeviceServiceImpl extends AbstractCachedEntityService linuxMqttCommands.put(MQTT, v)); + Optional.ofNullable(getMqttPublishCommand(LINUX, MQTTS, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> linuxMqttCommands.put(MQTTS, v)); + + ObjectNode windowsMqttCommands = JacksonUtil.newObjectNode(); + Optional.ofNullable(getMqttPublishCommand(WINDOWS, MQTT, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> windowsMqttCommands.put(MQTT, v)); + Optional.ofNullable(getMqttPublishCommand(WINDOWS, MQTTS, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> windowsMqttCommands.put(MQTTS, v)); + + ObjectNode dockerMqttCommands = JacksonUtil.newObjectNode(); + Optional.ofNullable(getMqttPublishCommand(DOCKER, MQTT, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> dockerMqttCommands.put(MQTT, v)); + Optional.ofNullable(getMqttPublishCommand(DOCKER, MQTTS, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> dockerMqttCommands.put(MQTTS, v)); + + mqttCommands.set(LINUX, linuxMqttCommands); + mqttCommands.set(WINDOWS, windowsMqttCommands); + mqttCommands.set(DOCKER, dockerMqttCommands); + + return mqttCommands; + } + + private String getMqttPublishCommand(String os, String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + if (MQTTS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + return CHECK_DOCUMENTATION; + } + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); + if (properties == null || !properties.getEnabled()) { + return null; + } + String mqttHost = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); + String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); + switch (os) { + case LINUX: + return getMosquittoPubPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + case WINDOWS: + return getMosquittoPubPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + case DOCKER: + return getDockerMosquittoClientsPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + default: + throw new IllegalArgumentException("Unsupported operating system: " + os); + } + } + + private JsonNode getCoapTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) { + ObjectNode coapCommands = JacksonUtil.newObjectNode(); + + ObjectNode linuxCoapCommands = JacksonUtil.newObjectNode(); + Optional.ofNullable(getCoapPublishCommand(LINUX, COAP, defaultHostname, deviceCredentials)) + .ifPresent(v -> linuxCoapCommands.put(COAP, v)); + Optional.ofNullable(getCoapPublishCommand(LINUX, COAPS, defaultHostname, deviceCredentials)) + .ifPresent(v -> linuxCoapCommands.put(COAPS, v)); + + coapCommands.set(LINUX, linuxCoapCommands); + return coapCommands; + } + + private String getCoapPublishCommand(String os, String protocol, String defaultHostname, DeviceCredentials deviceCredentials) { + if (COAPS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + return CHECK_DOCUMENTATION; + } + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); + if (properties == null || !properties.getEnabled()) { + return null; + } + String hostName = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); + String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); + + switch (os) { + case LINUX: + return getCoapClientCommand(protocol, hostName, port, deviceCredentials); + default: + throw new IllegalArgumentException("Unsupported operating system: " + os); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java index 3257ea13d6..72eac8bdea 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java @@ -24,10 +24,13 @@ public class DeviceConnectivityUtil { public static final String HTTP = "http"; public static final String HTTPS = "https"; public static final String MQTT = "mqtt"; + public static final String LINUX = "linux"; + public static final String WINDOWS = "windows"; + public static final String DOCKER = "docker"; public static final String MQTTS = "mqtts"; public static final String COAP = "coap"; public static final String COAPS = "coaps"; - public static final String SERVER_CHAIN_PEM = "serverChainPem"; + public static final String MQTT_SSL_PEM_FILE_NAME = "tb-server-chain.pem"; public static final String CHECK_DOCUMENTATION = "Check documentation"; public static final String JSON_EXAMPLE_PAYLOAD = "\"{temperature:25}\""; @@ -36,10 +39,10 @@ public class DeviceConnectivityUtil { protocol, host, port, deviceCredentials.getCredentialsId()); } - public static String getMosquittoPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials, String payload) { + public static String getMosquittoPubPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { StringBuilder command = new StringBuilder("mosquitto_pub -d -q 1"); if (MQTTS.equals(protocol)) { - command.append(" --cafile tb-server-chain.pem"); + command.append(" --cafile pathToFile/" + MQTT_SSL_PEM_FILE_NAME); } command.append(" -h ").append(host).append(port == null ? "" : " -p " + port); command.append(" -t ").append(deviceTelemetryTopic); @@ -68,7 +71,47 @@ public class DeviceConnectivityUtil { default: return null; } - command.append(payload); + command.append(" -m " + JSON_EXAMPLE_PAYLOAD); + return command.toString(); + } + + public static String getDockerMosquittoClientsPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + StringBuilder command = new StringBuilder("docker run"); + if (MQTTS.equals(protocol)) { + command.append(" --volume pathToFile/" + MQTT_SSL_PEM_FILE_NAME + ":/tmp/" + MQTT_SSL_PEM_FILE_NAME); + } + command.append(" -it --rm thingsboard/mosquitto-clients pub"); + if (MQTTS.equals(protocol)) { + command.append(" --cafile tmp/" + MQTT_SSL_PEM_FILE_NAME); + } + command.append(" -h ").append(host).append(port == null ? "" : " -p " + port); + command.append(" -t ").append(deviceTelemetryTopic); + + switch (deviceCredentials.getCredentialsType()) { + case ACCESS_TOKEN: + command.append(" -u ").append(deviceCredentials.getCredentialsId()); + break; + case MQTT_BASIC: + BasicMqttCredentials credentials = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), + BasicMqttCredentials.class); + if (credentials != null) { + if (credentials.getClientId() != null) { + command.append(" -i ").append(credentials.getClientId()); + } + if (credentials.getUserName() != null) { + command.append(" -u ").append(credentials.getUserName()); + } + if (credentials.getPassword() != null) { + command.append(" -P ").append(credentials.getPassword()); + } + } else { + return null; + } + break; + default: + return null; + } + command.append(" -m " + JSON_EXAMPLE_PAYLOAD); return command.toString(); } From 9c9cac9bd1969499cac16c3f08227e6d7ce2b5c6 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 20 Jul 2023 13:54:31 +0300 Subject: [PATCH 056/166] fix entity view delete test --- .../server/controller/EntityViewControllerTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java index a65769f3f1..cb41019f6f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java @@ -43,6 +43,7 @@ import org.springframework.test.web.servlet.ResultActions; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.StringUtils; @@ -253,7 +254,7 @@ public class EntityViewControllerTest extends AbstractControllerTest { doGet("/api/entityView/" + entityIdStr) .andExpect(status().isNotFound()) - .andExpect(statusReason(containsString(msgErrorNoFound("Entity view",entityIdStr)))); + .andExpect(statusReason(containsString(msgErrorNoFound(EntityType.ENTITY_VIEW.getNormalName(), entityIdStr)))); } @Test @@ -425,12 +426,12 @@ public class EntityViewControllerTest extends AbstractControllerTest { testNotifyEntityBroadcastEntityStateChangeEventMany(new EntityView(), new EntityView(), tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, - ActionType.ADDED, ActionType.ADDED, cntEntity, 0, cntEntity*2, 0); + ActionType.ADDED, ActionType.ADDED, cntEntity, 0, cntEntity * 2, 0); testNotifyEntityBroadcastEntityStateChangeEventMany(new EntityView(), new EntityView(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ASSIGNED_TO_CUSTOMER, ActionType.ASSIGNED_TO_CUSTOMER, cntEntity, cntEntity, - cntEntity*2, 3); + cntEntity * 2, 3); } @Test From c913b08b53b863e5c7d249ebfc5cf61ece5880e8 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 20 Jul 2023 15:13:11 +0300 Subject: [PATCH 057/166] UI: Fixed entity select component --- .../components/entity/entity-select.component.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index ccc4a0a079..9427c2ff73 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -125,16 +125,23 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte if (value.id === NULL_UUID) { value.id = null; } + if (value.entityType === AliasEntityType.CURRENT_TENANT + || value.entityType === AliasEntityType.CURRENT_USER + || value.entityType === AliasEntityType.CURRENT_USER_OWNER) { + value.id = NULL_UUID; + } else if (value.entityType === AliasEntityType.CURRENT_CUSTOMER && !value.id) { + this.modelValue.id = NULL_UUID; + } this.modelValue = value; - this.entitySelectFormGroup.get('entityType').patchValue(value.entityType, {emitEvent: true}); - this.entitySelectFormGroup.get('entityId').patchValue(value, {emitEvent: true}); + this.entitySelectFormGroup.get('entityType').patchValue(value.entityType, {emitEvent: false}); + this.entitySelectFormGroup.get('entityId').patchValue(value, {emitEvent: false}); } else { this.modelValue = { entityType: this.defaultEntityType, id: null }; - this.entitySelectFormGroup.get('entityType').patchValue(this.defaultEntityType, {emitEvent: true}); - this.entitySelectFormGroup.get('entityId').patchValue(null, {emitEvent: true}); + this.entitySelectFormGroup.get('entityType').patchValue(this.defaultEntityType, {emitEvent: false}); + this.entitySelectFormGroup.get('entityId').patchValue(null, {emitEvent: false}); } } From e3ef58c6038dd2e9e949ffaeb18a7d8991611f69 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 20 Jul 2023 15:33:02 +0300 Subject: [PATCH 058/166] added notnull check for http commands --- .../server/dao/device/DeviceСonnectivityServiceImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java index 7ae49276b8..e062441559 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java @@ -119,8 +119,10 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService private JsonNode getHttpTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) { ObjectNode httpCommands = JacksonUtil.newObjectNode(); - httpCommands.put(HTTP, getHttpPublishCommand(HTTP, defaultHostname, deviceCredentials)); - httpCommands.put(HTTPS, getHttpPublishCommand(HTTPS, defaultHostname, deviceCredentials)); + Optional.ofNullable(getHttpPublishCommand(HTTP, defaultHostname, deviceCredentials)) + .ifPresent(v -> httpCommands.put(HTTP, v)); + Optional.ofNullable(getHttpPublishCommand(HTTPS, defaultHostname, deviceCredentials)) + .ifPresent(v -> httpCommands.put(HTTPS, v)); return httpCommands; } From 1c601a6e7ded514f791550c389441ef1ff66cb27 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 20 Jul 2023 16:39:44 +0300 Subject: [PATCH 059/166] UI: Change field label assign customer --- .../home/components/wizard/device-wizard-dialog.component.html | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 8d55b4bc6a..08d71e1229 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -76,7 +76,7 @@ diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index c5ec1fca40..5254d7b654 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -937,7 +937,8 @@ "search": "Search customers", "selected-customers": "{ count, plural, =1 {1 customer} other {# customers} } selected", "edges": "Customer edge instances", - "manage-edges": "Manage edges" + "manage-edges": "Manage edges", + "assign-customer": "Assign customer" }, "datetime": { "date-from": "Date from", From fc499c74e3599d49f1349479c02947f80efbeddc Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 20 Jul 2023 17:22:33 +0300 Subject: [PATCH 060/166] deleted redundant imports --- .../server/controller/DeviceController.java | 2 -- .../server/controller/DeviceControllerTest.java | 10 ---------- .../thingsboard/server/dao/device/DeviceService.java | 2 -- .../server/dao/device/DeviceServiceImpl.java | 1 - 4 files changed, 15 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 07adb1ef1c..3eb6202aea 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -73,7 +73,6 @@ import org.thingsboard.server.service.entitiy.device.TbDeviceService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.annotation.Nullable; import javax.validation.Valid; @@ -135,7 +134,6 @@ public class DeviceController extends BaseController { private final TbDeviceService tbDeviceService; - private final SystemSecurityService systemSecurityService; @ApiOperation(value = "Get Device (getDeviceById)", notes = "Fetch the Device object based on the provided Device Id. " + diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 287e383317..9ab5f7fde8 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -34,15 +34,12 @@ import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileType; -import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackageInfo; @@ -52,16 +49,10 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; -import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceCredentialsId; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -93,7 +84,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; - @ContextConfiguration(classes = {DeviceControllerTest.Config.class}) @DaoSqlTest public class DeviceControllerTest extends AbstractControllerTest { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index 510250d264..a90ea9a572 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -36,9 +36,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.entity.EntityDaoService; -import java.net.URISyntaxException; import java.util.List; -import java.util.Map; import java.util.UUID; public interface DeviceService extends EntityDaoService { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 5a6caefd58..3f9ee12dda 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -96,7 +96,6 @@ public class DeviceServiceImpl extends AbstractCachedEntityService Date: Fri, 21 Jul 2023 10:32:08 +0300 Subject: [PATCH 061/166] UI: Remove translate --- ui-ngx/src/assets/locale/locale.constant-ca_ES.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-cs_CZ.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-da_DK.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-en_US.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-es_ES.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-fr_FR.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-ko_KR.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-sl_SI.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-tr_TR.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-zh_CN.json | 3 +-- ui-ngx/src/assets/locale/locale.constant-zh_TW.json | 3 +-- 11 files changed, 11 insertions(+), 22 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json index 349d13da2c..77edc26187 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json @@ -1376,8 +1376,7 @@ "device-configuration": "Configuració del dispositiu", "transport-configuration": "Configuració del transport", "wizard": { - "device-details": "Detalls del dispositiu", - "customer-to-assign-device": "Client al que assignar el dispositiu" + "device-details": "Detalls del dispositiu" }, "unassign-devices-from-edge-title": "Està segur de que desitja desassignar {count, plural, =1 {1 dispositivo} other {# dispositivos} }?", "unassign-devices-from-edge-text": "Després de la confirmació, tots els dispositius seleccionats quedaran sense assignar i la vora no podrà accedir a ells." diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index 52873b4d70..d89971cd0c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -1018,8 +1018,7 @@ "device-configuration": "Konfigurace zařízení", "transport-configuration": "Konfigurace přenosu", "wizard": { - "device-details": "Detail zařízení", - "customer-to-assign-device": "Přiřadit zařízení zákazníkovi" + "device-details": "Detail zařízení" }, "unassign-devices-from-edge-title": "Jste se jisti, že chcete odebrat { count, plural, =1 {1 zařízení} other {# zařízení} }?", "unassign-devices-from-edge-text": "Po potvrzení budou všechna vybraná zařízení odebrána a nebudou pro edge dostupná." diff --git a/ui-ngx/src/assets/locale/locale.constant-da_DK.json b/ui-ngx/src/assets/locale/locale.constant-da_DK.json index 2c1df70902..4e3b3ca779 100644 --- a/ui-ngx/src/assets/locale/locale.constant-da_DK.json +++ b/ui-ngx/src/assets/locale/locale.constant-da_DK.json @@ -1095,8 +1095,7 @@ "device-configuration": "Enhedskonfiguration", "transport-configuration": "Transportkonfiguration", "wizard": { - "device-details": "Enhedsoplysninger", - "customer-to-assign-device": "Kunden skal tildele enheden" + "device-details": "Enhedsoplysninger" } }, "device-profile": { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 5254d7b654..e0c07480cf 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1375,8 +1375,7 @@ "device-configuration": "Device configuration", "transport-configuration": "Transport configuration", "wizard": { - "device-details": "Device details", - "customer-to-assign-device": "Customer to assign the device" + "device-details": "Device details" }, "unassign-devices-from-edge-title": "Are you sure you want to unassign { count, plural, =1 {1 device} other {# devices} }?", "unassign-devices-from-edge-text": "After the confirmation all selected devices will be unassigned and won't be accessible by the edge." diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index 6518e03f58..d579de1a1f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -1325,8 +1325,7 @@ "device-configuration": "Configuración del dispositivo", "transport-configuration": "Configuración del transporte", "wizard": { - "device-details": "Detalles del dispositivo", - "customer-to-assign-device": "Cliente al que asignar el dispositivo" + "device-details": "Detalles del dispositivo" }, "unassign-devices-from-edge-title": "¿Está seguro de que desea desasignar {count, plural, =1 {1 dispositivo} other {# dispositivos} }?", "unassign-devices-from-edge-text": "Después de la confirmación, todos los dispositivos seleccionados quedarán sin asignar y el Edge no podrá acceder a ellos." diff --git a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json index 19f92e5a7c..56712eeeb5 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json @@ -1053,8 +1053,7 @@ "device-configuration": "Configuration du dipositif", "transport-configuration": "Configuration du transport", "wizard": { - "device-details": "Détails du dispositif", - "customer-to-assign-device": "Client auquel assigner le dispositif" + "device-details": "Détails du dispositif" } }, "device-profile": { diff --git a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json index 758482f578..0fd1ba039d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json +++ b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json @@ -913,8 +913,7 @@ "device-configuration": "장치 설정", "transport-configuration": "전송 설정", "wizard": { - "device-details": "장치 상세 정보", - "customer-to-assign-device": "장치에 할당할 커스터머" + "device-details": "장치 상세 정보" } }, "device-profile": { diff --git a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json index 8aced0ddc6..fcc2f6f867 100644 --- a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json +++ b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json @@ -913,8 +913,7 @@ "device-configuration": "Device configuration", "transport-configuration": "Transport configuration", "wizard": { - "device-details": "Device details", - "customer-to-assign-device": "Customer to assign the device" + "device-details": "Device details" } }, "device-profile": { diff --git a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json index b175a2d51a..cd7e31e97f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json +++ b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json @@ -1021,8 +1021,7 @@ "device-configuration": "Cihaz yapılandırması", "transport-configuration": "Aktarım yapılandırması", "wizard": { - "device-details": "Cihaz ayrıntıları", - "customer-to-assign-device": "Cihazı atamak için kullanıcı grubu" + "device-details": "Cihaz ayrıntıları" }, "unassign-devices-from-edge-title": "{ count, plural, =1 {1 cihazın} other {# cihazın} } atamasını kaldırmak istediğinizden emin misiniz?", "unassign-devices-from-edge-text": "Onaydan sonra, seçilen tüm cihazların ataması kaldırılacak ve uç tarafından erişilemeyecek." diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index b39e10e46c..2a9645f982 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -1221,8 +1221,7 @@ "device-configuration": "设备配置", "transport-configuration": "传输配置", "wizard": { - "device-details": "设备详细信息", - "customer-to-assign-device": "客户分配设备" + "device-details": "设备详细信息" }, "unassign-devices-from-edge-title": "确定要取消分配 { count, plural, =1 {1 个设备} other {# 个设备} } 吗?", "unassign-devices-from-edge-text": "确认后,设备将被取消分配,边缘将无法访问。" diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json index f2cce81824..3bc75f062c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json @@ -1134,8 +1134,7 @@ "device-configuration": "設備配置", "transport-configuration": "傳輸配置", "wizard": { - "device-details": "設備詳情", - "customer-to-assign-device": "客戶指定設備" + "device-details": "設備詳情" }, "unassign-devices-from-edge-title": "您確定要解除邊緣設備 { count, plural, =1 {1 device} other {# devices} }的指定嗎?", "unassign-devices-from-edge-text": "確認後邊緣指定設備將解除指定及其所有相關資料將無法恢復。" From d99c08fbbbde10a151b19668fc27e9bcfbb39d72 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 21 Jul 2023 12:52:15 +0300 Subject: [PATCH 062/166] changed response data structure --- .../DeviceConnectivityControllerTest.java | 38 +++------- .../DeviceСonnectivityServiceImpl.java | 75 +++++++++---------- 2 files changed, 45 insertions(+), 68 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index 8e27857878..3b10695e62 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -213,7 +213,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { credentials.getCredentialsId())); - JsonNode linuxMqttCommands = commands.get(MQTT).get(LINUX); + JsonNode linuxMqttCommands = commands.get(MQTT); assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + "-u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); @@ -221,11 +221,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - JsonNode windowsMqttCommands = commands.get(MQTT).get(WINDOWS); - assertThat(windowsMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + - "-u %s -m \"{temperature:25}\"", - credentials.getCredentialsId())); - JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + @@ -235,13 +230,11 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - JsonNode linuxCoapCommands = commands.get(COAP).get(LINUX); + JsonNode linuxCoapCommands = commands.get(COAP); assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry " + - "-t json -e \"{temperature:25}\"", - credentials.getCredentialsId())); + "-t json -e \"{temperature:25}\"", credentials.getCredentialsId())); assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry" + - " -t json -e \"{temperature:25}\"", - credentials.getCredentialsId())); + " -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } @Test @@ -258,7 +251,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); assertThat(commands).hasSize(1); - JsonNode linuxMqttCommands = commands.get(MQTT).get(LINUX); + JsonNode linuxMqttCommands = commands.get(MQTT); assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + "-u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); @@ -266,11 +259,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { "-t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - JsonNode windowsMqttCommands = commands.get(MQTT).get(WINDOWS); - assertThat(windowsMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + - "-u %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + @@ -303,12 +291,11 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doPost("/api/device/credentials", credentials) .andExpect(status().isOk()); - JsonNode commands = doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); assertThat(commands).hasSize(1); - JsonNode linuxMqttCommands = commands.get(MQTT).get(LINUX); + JsonNode linuxMqttCommands = commands.get(MQTT); assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + "-i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); @@ -316,12 +303,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { "-t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - JsonNode windowsMqttCommands = commands.get(MQTT).get(WINDOWS); - assertThat(windowsMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + - "-i %s -u %s -P %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - - JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + " -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", @@ -349,8 +330,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { JsonNode commands = doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); assertThat(commands).hasSize(1); - assertThat(commands.get(MQTT).get(LINUX).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); - assertThat(commands.get(MQTT).get(WINDOWS).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(MQTT).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); assertThat(commands.get(MQTT).get(DOCKER).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); } @@ -368,7 +348,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); assertThat(commands).hasSize(1); - JsonNode linuxCommands = commands.get(COAP).get(LINUX); + JsonNode linuxCommands = commands.get(COAP); assertThat(linuxCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); assertThat(linuxCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", @@ -393,6 +373,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { JsonNode commands = doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); assertThat(commands).hasSize(1); - assertThat(commands.get(COAP).get(LINUX).get(COAPS).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(COAP).get(COAPS).asText()).isEqualTo(CHECK_DOCUMENTATION); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java index e062441559..694f1cb8ee 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java @@ -85,22 +85,27 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService ObjectNode commands = JacksonUtil.newObjectNode(); switch (transportType) { case DEFAULT: - commands.set(HTTP, getHttpTransportPublishCommands(defaultHostname, creds)); - commands.set(MQTT, getMqttTransportPublishCommands(defaultHostname, creds)); - commands.set(COAP, getCoapTransportPublishCommands(defaultHostname, creds)); + Optional.ofNullable(getHttpTransportPublishCommands(defaultHostname, creds)) + .ifPresent(v -> commands.set(HTTP, v)); + Optional.ofNullable(getMqttTransportPublishCommands(defaultHostname, creds)) + .ifPresent(v -> commands.set(MQTT, v)); + Optional.ofNullable(getCoapTransportPublishCommands(defaultHostname, creds)) + .ifPresent(v -> commands.set(COAP, v)); break; case MQTT: MqttDeviceProfileTransportConfiguration transportConfiguration = (MqttDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); String topicName = transportConfiguration.getDeviceTelemetryTopic(); - commands.set(MQTT, getMqttTransportPublishCommands(defaultHostname, topicName, creds)); + Optional.ofNullable(getMqttTransportPublishCommands(defaultHostname, topicName, creds)) + .ifPresent(v -> commands.set(MQTT, v)); break; case COAP: - commands.set(COAP, getCoapTransportPublishCommands(defaultHostname, creds)); + Optional.ofNullable(getCoapTransportPublishCommands(defaultHostname, creds)) + .ifPresent(v -> commands.set(COAP, v)); break; default: - commands.set(transportType.name(), JacksonUtil.toJsonNode(CHECK_DOCUMENTATION)); + commands.put(transportType.name(), CHECK_DOCUMENTATION); } return commands; } @@ -123,7 +128,7 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService .ifPresent(v -> httpCommands.put(HTTP, v)); Optional.ofNullable(getHttpPublishCommand(HTTPS, defaultHostname, deviceCredentials)) .ifPresent(v -> httpCommands.put(HTTPS, v)); - return httpCommands; + return httpCommands.isEmpty() ? null : httpCommands; } private String getHttpPublishCommand(String protocol, String defaultHostname, DeviceCredentials deviceCredentials) { @@ -145,32 +150,22 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService private JsonNode getMqttTransportPublishCommands(String defaultHostname, String topic, DeviceCredentials deviceCredentials) { ObjectNode mqttCommands = JacksonUtil.newObjectNode(); - ObjectNode linuxMqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getMqttPublishCommand(LINUX, MQTT, defaultHostname, topic, deviceCredentials)) - .ifPresent(v -> linuxMqttCommands.put(MQTT, v)); - Optional.ofNullable(getMqttPublishCommand(LINUX, MQTTS, defaultHostname, topic, deviceCredentials)) - .ifPresent(v -> linuxMqttCommands.put(MQTTS, v)); - - ObjectNode windowsMqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getMqttPublishCommand(WINDOWS, MQTT, defaultHostname, topic, deviceCredentials)) - .ifPresent(v -> windowsMqttCommands.put(MQTT, v)); - Optional.ofNullable(getMqttPublishCommand(WINDOWS, MQTTS, defaultHostname, topic, deviceCredentials)) - .ifPresent(v -> windowsMqttCommands.put(MQTTS, v)); + Optional.ofNullable(getMqttPublishCommand(MQTT, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> mqttCommands.put(MQTT, v)); + Optional.ofNullable(getMqttPublishCommand(MQTTS, defaultHostname, topic, deviceCredentials)) + .ifPresent(v -> mqttCommands.put(MQTTS, v)); ObjectNode dockerMqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getMqttPublishCommand(DOCKER, MQTT, defaultHostname, topic, deviceCredentials)) + Optional.ofNullable(getDockerMqttPublishCommand(MQTT, defaultHostname, topic, deviceCredentials)) .ifPresent(v -> dockerMqttCommands.put(MQTT, v)); - Optional.ofNullable(getMqttPublishCommand(DOCKER, MQTTS, defaultHostname, topic, deviceCredentials)) + Optional.ofNullable(getDockerMqttPublishCommand(MQTTS, defaultHostname, topic, deviceCredentials)) .ifPresent(v -> dockerMqttCommands.put(MQTTS, v)); - mqttCommands.set(LINUX, linuxMqttCommands); - mqttCommands.set(WINDOWS, windowsMqttCommands); mqttCommands.set(DOCKER, dockerMqttCommands); - - return mqttCommands; + return mqttCommands.isEmpty() ? null : mqttCommands; } - private String getMqttPublishCommand(String os, String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + private String getMqttPublishCommand(String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { if (MQTTS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { return CHECK_DOCUMENTATION; } @@ -180,29 +175,31 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService } String mqttHost = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); - switch (os) { - case LINUX: - return getMosquittoPubPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); - case WINDOWS: - return getMosquittoPubPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); - case DOCKER: - return getDockerMosquittoClientsPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); - default: - throw new IllegalArgumentException("Unsupported operating system: " + os); + return getMosquittoPubPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + } + + private String getDockerMqttPublishCommand(String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + if (MQTTS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + return CHECK_DOCUMENTATION; + } + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); + if (properties == null || !properties.getEnabled()) { + return null; } + String mqttHost = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); + String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); + return getDockerMosquittoClientsPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); } private JsonNode getCoapTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) { ObjectNode coapCommands = JacksonUtil.newObjectNode(); - ObjectNode linuxCoapCommands = JacksonUtil.newObjectNode(); Optional.ofNullable(getCoapPublishCommand(LINUX, COAP, defaultHostname, deviceCredentials)) - .ifPresent(v -> linuxCoapCommands.put(COAP, v)); + .ifPresent(v -> coapCommands.put(COAP, v)); Optional.ofNullable(getCoapPublishCommand(LINUX, COAPS, defaultHostname, deviceCredentials)) - .ifPresent(v -> linuxCoapCommands.put(COAPS, v)); + .ifPresent(v -> coapCommands.put(COAPS, v)); - coapCommands.set(LINUX, linuxCoapCommands); - return coapCommands; + return coapCommands.isEmpty() ? null : coapCommands; } private String getCoapPublishCommand(String os, String protocol, String defaultHostname, DeviceCredentials deviceCredentials) { From 2ad30336ea214307e9c5dc86d43b64bf17987877 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 21 Jul 2023 13:51:49 +0300 Subject: [PATCH 063/166] deleted valur for mqqtt docker command when creds are X509 --- .../controller/DeviceConnectivityControllerTest.java | 8 ++++---- .../server/dao/device/DeviceСonnectivityServiceImpl.java | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index 3b10695e62..b138778025 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -213,11 +213,11 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { credentials.getCredentialsId())); - JsonNode linuxMqttCommands = commands.get(MQTT); - assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + + JsonNode mqttCommands = commands.get(MQTT); + assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + "-u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(linuxMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + + assertThat(mqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); @@ -331,7 +331,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); assertThat(commands).hasSize(1); assertThat(commands.get(MQTT).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); - assertThat(commands.get(MQTT).get(DOCKER).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(MQTT).get(DOCKER)).isNull(); } @Test diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java index 694f1cb8ee..284115ffb2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java @@ -161,7 +161,9 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService Optional.ofNullable(getDockerMqttPublishCommand(MQTTS, defaultHostname, topic, deviceCredentials)) .ifPresent(v -> dockerMqttCommands.put(MQTTS, v)); - mqttCommands.set(DOCKER, dockerMqttCommands); + if (!dockerMqttCommands.isEmpty()) { + mqttCommands.set(DOCKER, dockerMqttCommands); + } return mqttCommands.isEmpty() ? null : mqttCommands; } @@ -179,9 +181,6 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService } private String getDockerMqttPublishCommand(String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { - if (MQTTS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { - return CHECK_DOCUMENTATION; - } DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); if (properties == null || !properties.getEnabled()) { return null; From 9d20fa7d9e2c4857a1102a90ef3e5c5521df60e1 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 21 Jul 2023 16:12:34 +0300 Subject: [PATCH 064/166] added additional validation to ActionTypeTest and TbMsgTypeTest --- .../thingsboard/server/common/data/audit/ActionTypeTest.java | 2 ++ .../org/thingsboard/server/common/data/msg/TbMsgTypeTest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java index b76b0fc2b7..8c602c3cff 100644 --- a/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java +++ b/common/data/src/test/java/org/thingsboard/server/common/data/audit/ActionTypeTest.java @@ -56,6 +56,8 @@ class ActionTypeTest { for (var type : types) { if (typesWithNullRuleEngineMsgType.contains(type)) { assertThat(type.getRuleEngineMsgType()).isEmpty(); + } else { + assertThat(type.getRuleEngineMsgType()).isPresent(); } } } diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java index 1323b7359d..a37eb31d72 100644 --- a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java +++ b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java @@ -62,6 +62,8 @@ class TbMsgTypeTest { for (var type : tbMsgTypes) { if (typesWithNullRuleNodeConnection.contains(type)) { assertThat(type.getRuleNodeConnection()).isNull(); + } else { + assertThat(type.getRuleNodeConnection()).isNotNull(); } } } From d52b67cc1b6c569fea2aa6f046971345fbb6a10b Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 21 Jul 2023 16:29:04 +0300 Subject: [PATCH 065/166] UI: Refactoring --- .../components/entity/entity-select.component.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index 9427c2ff73..93452e4620 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -122,16 +122,6 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte writeValue(value: EntityId | null): void { if (value != null) { - if (value.id === NULL_UUID) { - value.id = null; - } - if (value.entityType === AliasEntityType.CURRENT_TENANT - || value.entityType === AliasEntityType.CURRENT_USER - || value.entityType === AliasEntityType.CURRENT_USER_OWNER) { - value.id = NULL_UUID; - } else if (value.entityType === AliasEntityType.CURRENT_CUSTOMER && !value.id) { - this.modelValue.id = NULL_UUID; - } this.modelValue = value; this.entitySelectFormGroup.get('entityType').patchValue(value.entityType, {emitEvent: false}); this.entitySelectFormGroup.get('entityId').patchValue(value, {emitEvent: false}); @@ -156,8 +146,6 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte || this.modelValue.entityType === AliasEntityType.CURRENT_USER || this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER) { this.modelValue.id = NULL_UUID; - } else if (this.modelValue.entityType === AliasEntityType.CURRENT_CUSTOMER && !this.modelValue.id) { - this.modelValue.id = NULL_UUID; } if (this.modelValue.entityType && this.modelValue.id) { From 26044fc0358e11de99ad9cededc5ef5e1f87adec Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 21 Jul 2023 17:53:21 +0300 Subject: [PATCH 066/166] UI: Updated show device connectivity commands and detect operating system from user --- ui-ngx/src/app/app.component.ts | 5 + ui-ngx/src/app/core/http/device.service.ts | 10 +- ui-ngx/src/app/core/utils.ts | 26 +- ...e-check-connectivity-dialog.component.html | 363 +++++++++++++----- ...e-check-connectivity-dialog.component.scss | 19 +- ...ice-check-connectivity-dialog.component.ts | 89 ++++- ui-ngx/src/app/shared/models/device.models.ts | 25 ++ ui-ngx/src/assets/docker.svg | 1 + .../help/en_US/device/install_coap_client.md | 40 -- .../assets/help/en_US/device/install_curl.md | 34 -- .../help/en_US/device/install_mqtt_client.md | 38 -- ui-ngx/src/assets/linux.svg | 1 + .../assets/locale/locale.constant-en_US.json | 15 +- ui-ngx/src/assets/macos.svg | 1 + ui-ngx/src/assets/windows.svg | 1 + ui-ngx/src/form.scss | 7 + 16 files changed, 435 insertions(+), 240 deletions(-) create mode 100644 ui-ngx/src/assets/docker.svg delete mode 100644 ui-ngx/src/assets/help/en_US/device/install_coap_client.md delete mode 100644 ui-ngx/src/assets/help/en_US/device/install_curl.md delete mode 100644 ui-ngx/src/assets/help/en_US/device/install_mqtt_client.md create mode 100644 ui-ngx/src/assets/linux.svg create mode 100644 ui-ngx/src/assets/macos.svg create mode 100644 ui-ngx/src/assets/windows.svg diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index 8f612da5a1..627fc53608 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -94,6 +94,11 @@ export class AppComponent implements OnInit { ) ); + this.matIconRegistry.addSvgIcon('windows', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/windows.svg')); + this.matIconRegistry.addSvgIcon('macos', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/macos.svg')); + this.matIconRegistry.addSvgIcon('linux', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/linux.svg')); + this.matIconRegistry.addSvgIcon('docker', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/docker.svg')); + this.storageService.testLocalStorage(); this.setupTranslate(); diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index 44e91e43f8..8dff1e7ebc 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -25,8 +25,10 @@ import { ClaimResult, Device, DeviceCredentials, - DeviceInfo, DeviceInfoQuery, - DeviceSearchQuery + DeviceInfo, + DeviceInfoQuery, + DeviceSearchQuery, + PublishTelemetryCommand } from '@app/shared/models/device.models'; import { EntitySubtype } from '@app/shared/models/entity-type.models'; import { AuthService } from '@core/auth/auth.service'; @@ -208,8 +210,8 @@ export class DeviceService { return this.http.post('/api/device/bulk_import', entitiesData, defaultHttpOptionsFromConfig(config)); } - public getDevicePublishTelemetryCommands(deviceId: string, config?: RequestConfig): Observable<{[key: string]: string}> { - return this.http.get<{[key: string]: string}>(`/api/device/${deviceId}/commands`, defaultHttpOptionsFromConfig(config)); + public getDevicePublishTelemetryCommands(deviceId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/device-connectivity/${deviceId}`, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index d6a3c3c6e3..c823c2bfea 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -355,9 +355,7 @@ const SNAKE_CASE_REGEXP = /[A-Z]/g; export function snakeCase(name: string, separator: string): string { separator = separator || '_'; - return name.replace(SNAKE_CASE_REGEXP, (letter, pos) => { - return (pos ? separator : '') + letter.toLowerCase(); - }); + return name.replace(SNAKE_CASE_REGEXP, (letter, pos) => (pos ? separator : '') + letter.toLowerCase()); } export function getDescendantProp(obj: any, path: string): any { @@ -776,3 +774,25 @@ export function genNextLabel(name: string, datasources: Datasource[]): string { } return label; } + +export const getOS = (): string => { + const userAgent = window.navigator.userAgent.toLowerCase(); + const macosPlatforms = /(macintosh|macintel|macppc|mac68k|macos|mac_powerpc)/i; + const windowsPlatforms = /(win32|win64|windows|wince)/i; + const iosPlatforms = /(iphone|ipad|ipod|darwin|ios)/i; + let os = null; + + if (macosPlatforms.test(userAgent)) { + os = 'macos'; + } else if (iosPlatforms.test(userAgent)) { + os = 'ios'; + } else if (windowsPlatforms.test(userAgent)) { + os = 'windows'; + } else if (/android/.test(userAgent)) { + os = 'android'; + } else if (/linux/.test(userAgent)) { + os = 'linux'; + } + + return os; +}; diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html index a0991570eb..a595487521 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html @@ -59,121 +59,235 @@ {{ deviceTransportTypeTranslationMap.get(DeviceTransportType.LWM2M) | translate }} -
@@ -186,14 +186,14 @@ - + Docker @@ -202,8 +202,8 @@
@@ -228,8 +228,8 @@ @@ -249,14 +249,14 @@
- + Docker @@ -265,8 +265,8 @@
From 8b19b5d1695c58ea958fbadd43b59dadf278c41f Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 21 Jul 2023 18:56:52 +0300 Subject: [PATCH 068/166] added curl command for mqtts --- .../ThingsboardSecurityConfiguration.java | 5 +- .../DeviceConnectivityController.java | 1 - .../DeviceConnectivityControllerTest.java | 51 +++++----- .../DeviceСonnectivityServiceImpl.java | 99 +++++++++++-------- .../dao/util/DeviceConnectivityUtil.java | 16 ++- 5 files changed, 100 insertions(+), 72 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index 793670f0ab..56a687be21 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -78,6 +78,7 @@ public class ThingsboardSecurityConfiguration { public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**"; public static final String MAIL_OAUTH2_PROCESSING_ENTRY_POINT = "/api/admin/mail/oauth2/code"; + public static final String DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT = "/api/device-connectivity/mqtts/certificate/download"; @Autowired private ThingsboardErrorResponseHandler restAccessDeniedHandler; @@ -136,7 +137,8 @@ public class ThingsboardSecurityConfiguration { protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception { List pathsToSkip = new ArrayList<>(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS)); pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT, - PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT)); + PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT, + DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT)); SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT); JwtTokenAuthenticationProcessingFilter filter = new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtHeaderTokenExtractor, matcher); @@ -204,6 +206,7 @@ public class ThingsboardSecurityConfiguration { .antMatchers(PUBLIC_LOGIN_ENTRY_POINT).permitAll() // Public login end-point .antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point .antMatchers(MAIL_OAUTH2_PROCESSING_ENTRY_POINT).permitAll() // Mail oauth2 code processing url + .antMatchers(DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT).permitAll() // Mail oauth2 code processing url .antMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points .and() .authorizeRequests() diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java index bf745a2033..c11efc05a4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java @@ -89,7 +89,6 @@ public class DeviceConnectivityController extends BaseController { } @ApiOperation(value = "Download mqtt ssl certificate using file path defined in device.connectivity properties (downloadMqttServerCertificate)", notes = "Download Mqtt server certificate." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/device-connectivity/{protocol}/certificate/download", method = RequestMethod.GET) @ResponseBody public ResponseEntity downloadMqttServerCertificate(@ApiParam(value = PROTOCOL_PARAM_DESCRIPTION) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index b138778025..05c14dbb8b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -217,17 +217,17 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + "-u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(mqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + - "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", - credentials.getCredentialsId())); - + assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl http://localhost:80/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); + assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tmp/tb-server-chain.pem -h localhost -p 8883 " + + "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + " -p 1883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --volume pathToFile/tb-server-chain.pem:/tmp/tb-server-chain.pem " + - "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients " + + "/bin/sh -c \"curl -o /tmp/tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + + "pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"\"", credentials.getCredentialsId())); JsonNode linuxCoapCommands = commands.get(COAP); @@ -251,21 +251,20 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); assertThat(commands).hasSize(1); - JsonNode linuxMqttCommands = commands.get(MQTT); - assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + - "-u %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - assertThat(linuxMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + - "-t %s -u %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - + JsonNode mqttCommands = commands.get(MQTT); + assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + + "-u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl http://localhost:80/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); + assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tmp/tb-server-chain.pem -h localhost -p 8883 " + + "-t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + " -p 1883 -t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --volume pathToFile/tb-server-chain.pem:/tmp/tb-server-chain.pem " + - "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"", + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients " + + "/bin/sh -c \"curl -o /tmp/tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + + "pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); } @@ -295,20 +294,20 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); assertThat(commands).hasSize(1); - JsonNode linuxMqttCommands = commands.get(MQTT); - assertThat(linuxMqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + - "-i %s -u %s -P %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - assertThat(linuxMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile pathToFile/tb-server-chain.pem -h localhost -p 8883 " + - "-t %s -i %s -u %s -P %s -m \"{temperature:25}\"", - DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + JsonNode mqttCommands = commands.get(MQTT); + assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + + "-i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); + assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl http://localhost:80/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); + assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tmp/tb-server-chain.pem -h localhost -p 8883 " + + "-t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + " -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --volume pathToFile/tb-server-chain.pem:/tmp/tb-server-chain.pem " + - "-it --rm thingsboard/mosquitto-clients pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients " + + "/bin/sh -c \"curl -o /tmp/tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + + "pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); } @@ -330,7 +329,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { JsonNode commands = doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); assertThat(commands).hasSize(1); - assertThat(commands.get(MQTT).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(MQTT).get(MQTTS).get(0).asText()).isEqualTo(CHECK_DOCUMENTATION); assertThat(commands.get(MQTT).get(DOCKER)).isNull(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java index 284115ffb2..e15056a2a6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.device; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; @@ -36,6 +37,9 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Optional; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -48,7 +52,6 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.LINUX; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.WINDOWS; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCoapClientCommand; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCurlCommand; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getDockerMosquittoClientsPublishCommand; @@ -77,7 +80,6 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService log.trace("Executing findDevicePublishTelemetryCommands [{}]", deviceId); validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); - String defaultHostname = new URI(baseUrl).getHost(); DeviceCredentials creds = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), deviceId); DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); DeviceTransportType transportType = deviceProfile.getTransportType(); @@ -85,11 +87,11 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService ObjectNode commands = JacksonUtil.newObjectNode(); switch (transportType) { case DEFAULT: - Optional.ofNullable(getHttpTransportPublishCommands(defaultHostname, creds)) + Optional.ofNullable(getHttpTransportPublishCommands(baseUrl, creds)) .ifPresent(v -> commands.set(HTTP, v)); - Optional.ofNullable(getMqttTransportPublishCommands(defaultHostname, creds)) + Optional.ofNullable(getMqttTransportPublishCommands(baseUrl, creds)) .ifPresent(v -> commands.set(MQTT, v)); - Optional.ofNullable(getCoapTransportPublishCommands(defaultHostname, creds)) + Optional.ofNullable(getCoapTransportPublishCommands(baseUrl, creds)) .ifPresent(v -> commands.set(COAP, v)); break; case MQTT: @@ -97,11 +99,11 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService (MqttDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); String topicName = transportConfiguration.getDeviceTelemetryTopic(); - Optional.ofNullable(getMqttTransportPublishCommands(defaultHostname, topicName, creds)) + Optional.ofNullable(getMqttTransportPublishCommands(baseUrl, topicName, creds)) .ifPresent(v -> commands.set(MQTT, v)); break; case COAP: - Optional.ofNullable(getCoapTransportPublishCommands(defaultHostname, creds)) + Optional.ofNullable(getCoapTransportPublishCommands(baseUrl, creds)) .ifPresent(v -> commands.set(COAP, v)); break; default: @@ -122,7 +124,7 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService } } - private JsonNode getHttpTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) { + private JsonNode getHttpTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) throws URISyntaxException { ObjectNode httpCommands = JacksonUtil.newObjectNode(); Optional.ofNullable(getHttpPublishCommand(HTTP, defaultHostname, deviceCredentials)) .ifPresent(v -> httpCommands.put(HTTP, v)); @@ -131,34 +133,37 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService return httpCommands.isEmpty() ? null : httpCommands; } - private String getHttpPublishCommand(String protocol, String defaultHostname, DeviceCredentials deviceCredentials) { + private String getHttpPublishCommand(String protocol, String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { DeviceConnectivityInfo httpProps = deviceConnectivityConfiguration.getConnectivity().get(protocol); if (httpProps == null || !httpProps.getEnabled() || deviceCredentials.getCredentialsType() != DeviceCredentialsType.ACCESS_TOKEN) { return null; } - String hostName = httpProps.getHost().isEmpty() ? defaultHostname : httpProps.getHost(); + String hostName = httpProps.getHost().isEmpty() ? new URI(baseUrl).getHost() : httpProps.getHost(); String port = httpProps.getPort().isEmpty() ? "" : ":" + httpProps.getPort(); return getCurlCommand(protocol, hostName, port, deviceCredentials); } - private JsonNode getMqttTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) { - return getMqttTransportPublishCommands(defaultHostname, DEFAULT_DEVICE_TELEMETRY_TOPIC, deviceCredentials); + private JsonNode getMqttTransportPublishCommands(String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { + return getMqttTransportPublishCommands(baseUrl, DEFAULT_DEVICE_TELEMETRY_TOPIC, deviceCredentials); } - private JsonNode getMqttTransportPublishCommands(String defaultHostname, String topic, DeviceCredentials deviceCredentials) { + private JsonNode getMqttTransportPublishCommands(String baseUrl, String topic, DeviceCredentials deviceCredentials) throws URISyntaxException { ObjectNode mqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getMqttPublishCommand(MQTT, defaultHostname, topic, deviceCredentials)) + Optional.ofNullable(getMqttPublishCommand(baseUrl, topic, deviceCredentials)) .ifPresent(v -> mqttCommands.put(MQTT, v)); - Optional.ofNullable(getMqttPublishCommand(MQTTS, defaultHostname, topic, deviceCredentials)) - .ifPresent(v -> mqttCommands.put(MQTTS, v)); + List mqttsPublishCommand = getMqttsPublishCommand(baseUrl, topic, deviceCredentials); + if (mqttsPublishCommand != null){ + ArrayNode arrayNode = mqttCommands.putArray(MQTTS); + mqttsPublishCommand.forEach(arrayNode::add); + } ObjectNode dockerMqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getDockerMqttPublishCommand(MQTT, defaultHostname, topic, deviceCredentials)) + Optional.ofNullable(getDockerMqttPublishCommand(MQTT,baseUrl, topic, deviceCredentials)) .ifPresent(v -> dockerMqttCommands.put(MQTT, v)); - Optional.ofNullable(getDockerMqttPublishCommand(MQTTS, defaultHostname, topic, deviceCredentials)) + Optional.ofNullable(getDockerMqttPublishCommand(MQTTS, baseUrl, topic, deviceCredentials)) .ifPresent(v -> dockerMqttCommands.put(MQTTS, v)); if (!dockerMqttCommands.isEmpty()) { @@ -167,41 +172,62 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService return mqttCommands.isEmpty() ? null : mqttCommands; } - private String getMqttPublishCommand(String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { - if (MQTTS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { - return CHECK_DOCUMENTATION; - } - DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); + private String getMqttPublishCommand(String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(MQTT); if (properties == null || !properties.getEnabled()) { return null; } - String mqttHost = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); + String mqttHost = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); - return getMosquittoPubPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + return getMosquittoPubPublishCommand(MQTT, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); } - private String getDockerMqttPublishCommand(String protocol, String defaultHostname, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + private List getMqttsPublishCommand(String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { + String pubCommand; + if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + return List.of(CHECK_DOCUMENTATION); + } else { + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(MQTTS); + if (properties == null || !properties.getEnabled()) { + return null; + } + String mqttHost = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); + String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); + pubCommand = getMosquittoPubPublishCommand(MQTTS, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + } + + ArrayList commands = new ArrayList<>(); + if (pubCommand != null) { + commands.add("curl " + baseUrl + "/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); + commands.add(pubCommand); + return commands; + } + return null; + } + + + private String getDockerMqttPublishCommand(String protocol, String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); if (properties == null || !properties.getEnabled()) { return null; } - String mqttHost = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); + String mqttHost = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); - return getDockerMosquittoClientsPublishCommand(protocol, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + return getDockerMosquittoClientsPublishCommand(protocol, baseUrl, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); } - private JsonNode getCoapTransportPublishCommands(String defaultHostname, DeviceCredentials deviceCredentials) { + private JsonNode getCoapTransportPublishCommands(String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { ObjectNode coapCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getCoapPublishCommand(LINUX, COAP, defaultHostname, deviceCredentials)) + Optional.ofNullable(getCoapPublishCommand(COAP, baseUrl, deviceCredentials)) .ifPresent(v -> coapCommands.put(COAP, v)); - Optional.ofNullable(getCoapPublishCommand(LINUX, COAPS, defaultHostname, deviceCredentials)) + Optional.ofNullable(getCoapPublishCommand(COAPS, baseUrl, deviceCredentials)) .ifPresent(v -> coapCommands.put(COAPS, v)); return coapCommands.isEmpty() ? null : coapCommands; } - private String getCoapPublishCommand(String os, String protocol, String defaultHostname, DeviceCredentials deviceCredentials) { + private String getCoapPublishCommand(String protocol, String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { if (COAPS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { return CHECK_DOCUMENTATION; } @@ -209,14 +235,9 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService if (properties == null || !properties.getEnabled()) { return null; } - String hostName = properties.getHost().isEmpty() ? defaultHostname : properties.getHost(); + String hostName = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); - switch (os) { - case LINUX: - return getCoapClientCommand(protocol, hostName, port, deviceCredentials); - default: - throw new IllegalArgumentException("Unsupported operating system: " + os); - } + return getCoapClientCommand(protocol, hostName, port, deviceCredentials); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java index 72eac8bdea..e99df56e64 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java @@ -19,6 +19,9 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials; +import java.util.ArrayList; +import java.util.List; + public class DeviceConnectivityUtil { public static final String HTTP = "http"; @@ -42,7 +45,7 @@ public class DeviceConnectivityUtil { public static String getMosquittoPubPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { StringBuilder command = new StringBuilder("mosquitto_pub -d -q 1"); if (MQTTS.equals(protocol)) { - command.append(" --cafile pathToFile/" + MQTT_SSL_PEM_FILE_NAME); + command.append(" --cafile tmp/" + MQTT_SSL_PEM_FILE_NAME); } command.append(" -h ").append(host).append(port == null ? "" : " -p " + port); command.append(" -t ").append(deviceTelemetryTopic); @@ -75,12 +78,12 @@ public class DeviceConnectivityUtil { return command.toString(); } - public static String getDockerMosquittoClientsPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { - StringBuilder command = new StringBuilder("docker run"); + public static String getDockerMosquittoClientsPublishCommand(String protocol, String baseUrl, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + StringBuilder command = new StringBuilder("docker run -it --rm thingsboard/mosquitto-clients "); if (MQTTS.equals(protocol)) { - command.append(" --volume pathToFile/" + MQTT_SSL_PEM_FILE_NAME + ":/tmp/" + MQTT_SSL_PEM_FILE_NAME); + command.append("/bin/sh -c \"curl -o /tmp/tb-server-chain.pem ").append(baseUrl).append("/api/device-connectivity/mqtts/certificate/download && "); } - command.append(" -it --rm thingsboard/mosquitto-clients pub"); + command.append("pub"); if (MQTTS.equals(protocol)) { command.append(" --cafile tmp/" + MQTT_SSL_PEM_FILE_NAME); } @@ -112,6 +115,9 @@ public class DeviceConnectivityUtil { return null; } command.append(" -m " + JSON_EXAMPLE_PAYLOAD); + if (MQTTS.equals(protocol)) { + command.append("\""); + } return command.toString(); } From 6a3be7fbaa61093409cb65a92446e9992823f6f3 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 24 Jul 2023 10:55:11 +0300 Subject: [PATCH 069/166] UI: fixed show commands in mqtt --- .../server/dao/device/DeviceСonnectivityServiceImpl.java | 8 ++++++-- .../server/dao/util/DeviceConnectivityUtil.java | 3 --- .../device-check-connectivity-dialog.component.scss | 1 + .../device/device-check-connectivity-dialog.component.ts | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java index e15056a2a6..e7bfefabbd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java @@ -156,8 +156,12 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService .ifPresent(v -> mqttCommands.put(MQTT, v)); List mqttsPublishCommand = getMqttsPublishCommand(baseUrl, topic, deviceCredentials); if (mqttsPublishCommand != null){ - ArrayNode arrayNode = mqttCommands.putArray(MQTTS); - mqttsPublishCommand.forEach(arrayNode::add); + if (mqttsPublishCommand.size() > 1) { + ArrayNode arrayNode = mqttCommands.putArray(MQTTS); + mqttsPublishCommand.forEach(arrayNode::add); + } else { + mqttCommands.put(MQTTS, mqttsPublishCommand.get(0)); + } } ObjectNode dockerMqttCommands = JacksonUtil.newObjectNode(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java index e99df56e64..dad405b093 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java @@ -19,9 +19,6 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials; -import java.util.ArrayList; -import java.util.List; - public class DeviceConnectivityUtil { public static final String HTTP = "http"; diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss index 1a95da0a14..e50b46d9fc 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss @@ -123,6 +123,7 @@ margin: 0; background: #F3F6FA; border-color: #305680; + padding-right: 38px; } } button.clipboard-btn { diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts index f185d88c6a..07da1a43ed 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts @@ -171,7 +171,7 @@ export class DeviceCheckConnectivityDialogComponent extends if (Array.isArray(commands)) { const formatCommands: Array = []; commands.forEach(command => formatCommands.push(this.createMarkDownSingleCommand(command))); - return formatCommands.join('
\n'); + return formatCommands.join(`\n
\n\n`); } else { return this.createMarkDownSingleCommand(commands); } From 017060886ebaa37bbcac2e2cc1198079ed012255 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 24 Jul 2023 12:52:19 +0300 Subject: [PATCH 070/166] UI: Fixed style check connectivity and text install --- .../device/device-check-connectivity-dialog.component.html | 7 ++++++- .../device/device-check-connectivity-dialog.component.scss | 7 +++++++ ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html index 01d2330aa3..11e1435119 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html @@ -140,7 +140,12 @@
device.connectivity.install-necessary-client-tools
-
Coming Soon!!!!
+
+ + +
Date: Mon, 24 Jul 2023 17:41:12 +0300 Subject: [PATCH 071/166] UI: Refactoring RouterTabsComponent for used children route --- ui-ngx/src/app/core/services/menu.service.ts | 84 ------------------- .../home/components/router-tabs.component.ts | 10 +++ .../modules/home/menu/side-menu.component.ts | 12 +-- .../pages/account/account-routing.module.ts | 3 +- .../assets/locale/locale.constant-en_US.json | 3 +- 5 files changed, 14 insertions(+), 98 deletions(-) diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 507ed01984..b33c552eb1 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -262,34 +262,6 @@ export class MenuService { isMdiIcon: true } ] - }, - { - id: 'account', - name: 'profile.profile', - type: 'link', - path: '/account', - disabled: true, - icon: 'mdi:message-badge', - isMdiIcon: true, - pages: [ - { - id: 'personal_info', - name: 'account.personal-info', - fullName: 'account.personal-info', - type: 'link', - path: '/account/profile', - icon: 'mdi:badge-account-horizontal', - isMdiIcon: true - }, - { - id: 'security', - name: 'security.security', - fullName: 'security.security', - type: 'link', - path: '/account/security', - icon: 'lock' - } - ] } ); return sections; @@ -662,34 +634,6 @@ export class MenuService { icon: 'track_changes' } ] - }, - { - id: 'account', - name: 'profile.profile', - type: 'link', - path: '/account', - disabled: true, - icon: 'mdi:message-badge', - isMdiIcon: true, - pages: [ - { - id: 'personal_info', - name: 'account.personal-info', - fullName: 'account.personal-info', - type: 'link', - path: '/account/profile', - icon: 'mdi:badge-account-horizontal', - isMdiIcon: true - }, - { - id: 'security', - name: 'security.security', - fullName: 'security.security', - type: 'link', - path: '/account/security', - icon: 'lock' - } - ] } ); return sections; @@ -941,34 +885,6 @@ export class MenuService { icon: 'inbox' } ] - }, - { - id: 'account', - name: 'profile.profile', - type: 'link', - path: '/account', - disabled: true, - icon: 'mdi:message-badge', - isMdiIcon: true, - pages: [ - { - id: 'personal_info', - name: 'account.personal-info', - fullName: 'account.personal-info', - type: 'link', - path: '/account/profile', - icon: 'mdi:badge-account-horizontal', - isMdiIcon: true - }, - { - id: 'security', - name: 'security.security', - fullName: 'security.security', - type: 'link', - path: '/account/security', - icon: 'lock' - } - ] } ); return sections; diff --git a/ui-ngx/src/app/modules/home/components/router-tabs.component.ts b/ui-ngx/src/app/modules/home/components/router-tabs.component.ts index 65c0074227..2cc6b0da81 100644 --- a/ui-ngx/src/app/modules/home/components/router-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/components/router-tabs.component.ts @@ -89,6 +89,16 @@ export class RouterTabsComponent extends PageComponent implements OnInit { const isRoot = rootPath === ''; const tabs: Array = found ? found.pages.filter(page => !page.disabled && (!page.rootOnly || isRoot)) : []; return tabs.map((tab) => ({...tab, path: rootPath + tab.path})); + } else if (activatedRoute.snapshot.data.useChildrenRoutesForTabs && sectionPath.endsWith(activatedRoute.routeConfig.path)) { + const activeRouterChildren = activatedRoute.routeConfig.children.filter(page => page.path !== ''); + return activeRouterChildren.map(tab => ({ + id: tab.component.name, + type: 'link', + name: tab.data?.breadcrumb?.label ?? '', + icon: tab.data?.breadcrumb?.icon ?? '', + isMdiIcon: tab.data?.breadcrumb?.icon.startsWith('mdi:') ?? false, + path: `${sectionPath}/${tab.path}` + })); } else { return []; } diff --git a/ui-ngx/src/app/modules/home/menu/side-menu.component.ts b/ui-ngx/src/app/modules/home/menu/side-menu.component.ts index 5e381d8f5c..f6e1f30624 100644 --- a/ui-ngx/src/app/modules/home/menu/side-menu.component.ts +++ b/ui-ngx/src/app/modules/home/menu/side-menu.component.ts @@ -17,8 +17,6 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { MenuService } from '@core/services/menu.service'; import { MenuSection } from '@core/services/menu.models'; -import { Observable } from 'rxjs'; -import { map, share } from 'rxjs/operators'; @Component({ selector: 'tb-side-menu', @@ -28,23 +26,15 @@ import { map, share } from 'rxjs/operators'; }) export class SideMenuComponent implements OnInit { - menuSections$: Observable>; + menuSections$ = this.menuService.menuSections(); constructor(private menuService: MenuService) { - this.menuSections$ = this.menuService.menuSections().pipe( - map((sections) => this.filterSections(sections)), - share() - ); } trackByMenuSection(index: number, section: MenuSection){ return section.id; } - private filterSections(sections: Array): Array { - return sections.filter(section => !section.disabled); - } - ngOnInit() { } diff --git a/ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts b/ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts index bb63b361b6..c12c958493 100644 --- a/ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts @@ -30,7 +30,8 @@ const routes: Routes = [ breadcrumb: { label: 'account.account', icon: 'account_circle' - } + }, + useChildrenRoutesForTabs: true }, children: [ { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 77db9c2198..2a0ab096a3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -11,8 +11,7 @@ "permission-denied-text": "You don't have permission to perform this operation!" }, "account": { - "account": "Account", - "personal-info": "Personal info" + "account": "Account" }, "action": { "activate": "Activate", From 3b8a9d94ecfffeb3813bcc75d203c568be4ab567 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Mon, 24 Jul 2023 22:57:52 +0200 Subject: [PATCH 072/166] Lwm2m transport - merge non-unique endpoints for models fetched from cache --- .../model/LwM2MModelConfigServiceImpl.java | 8 +- .../LwM2MModelConfigServiceImplTest.java | 73 +++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImplTest.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java index 302bd20c8b..eef9a53024 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImpl.java @@ -52,7 +52,7 @@ import java.util.stream.Collectors; public class LwM2MModelConfigServiceImpl implements LwM2MModelConfigService { @Autowired - private TbLwM2MModelConfigStore modelStore; + TbLwM2MModelConfigStore modelStore; @Autowired @Lazy @@ -67,14 +67,14 @@ public class LwM2MModelConfigServiceImpl implements LwM2MModelConfigService { @Autowired private LwM2MTelemetryLogService logService; - private ConcurrentMap currentModelConfigs; + ConcurrentMap currentModelConfigs; @AfterStartUp(order = AfterStartUp.BEFORE_TRANSPORT_SERVICE) - private void init() { + public void init() { List models = modelStore.getAll(); log.debug("Fetched model configs: {}", models); currentModelConfigs = models.stream() - .collect(Collectors.toConcurrentMap(LwM2MModelConfig::getEndpoint, m -> m)); + .collect(Collectors.toConcurrentMap(LwM2MModelConfig::getEndpoint, m -> m, (existing, replacement) -> existing)); } @Override diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImplTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImplTest.java new file mode 100644 index 0000000000..fc54ca9e0b --- /dev/null +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfigServiceImplTest.java @@ -0,0 +1,73 @@ +/** + * 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.transport.lwm2m.server.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MModelConfigStore; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.mock; + +class LwM2MModelConfigServiceImplTest { + + LwM2MModelConfigServiceImpl service; + TbLwM2MModelConfigStore modelStore; + + @BeforeEach + void setUp() { + service = new LwM2MModelConfigServiceImpl(); + modelStore = mock(TbLwM2MModelConfigStore.class); + service.modelStore = modelStore; + } + + @Test + void testInitWithDuplicatedModels() { + LwM2MModelConfig config = new LwM2MModelConfig("urn:imei:951358811362976"); + List models = List.of(config, config); + willReturn(models).given(modelStore).getAll(); + service.init(); + assertThat(service.currentModelConfigs).containsExactlyEntriesOf(Map.of(config.getEndpoint(), config)); + } + + @Test + void testInitWithNonUniqueEndpoints() { + LwM2MModelConfig configAlfa = new LwM2MModelConfig("urn:imei:951358811362976"); + LwM2MModelConfig configBravo = new LwM2MModelConfig("urn:imei:151358811362976"); + LwM2MModelConfig configDelta = new LwM2MModelConfig("urn:imei:151358811362976"); + assertThat(configBravo.getEndpoint()).as("non-unique endpoints provided").isEqualTo(configDelta.getEndpoint()); + List models = List.of(configAlfa, configBravo, configDelta); + willReturn(models).given(modelStore).getAll(); + service.init(); + assertThat(service.currentModelConfigs).containsExactlyInAnyOrderEntriesOf(Map.of( + configAlfa.getEndpoint(), configAlfa, + configBravo.getEndpoint(), configBravo + )); + } + + @Test + void testInitWithEmptyModels() { + willReturn(Collections.emptyList()).given(modelStore).getAll(); + service.init(); + assertThat(service.currentModelConfigs).isEmpty(); + } + +} From 152e2200f017cb1ea13627c9d6212ab7ce0e0f6d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 25 Jul 2023 10:11:04 +0300 Subject: [PATCH 073/166] UI: Clear code after merge --- ui-ngx/src/app/modules/home/components/router-tabs.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/router-tabs.component.ts b/ui-ngx/src/app/modules/home/components/router-tabs.component.ts index 2cc6b0da81..c5ffb11908 100644 --- a/ui-ngx/src/app/modules/home/components/router-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/components/router-tabs.component.ts @@ -96,7 +96,6 @@ export class RouterTabsComponent extends PageComponent implements OnInit { type: 'link', name: tab.data?.breadcrumb?.label ?? '', icon: tab.data?.breadcrumb?.icon ?? '', - isMdiIcon: tab.data?.breadcrumb?.icon.startsWith('mdi:') ?? false, path: `${sectionPath}/${tab.path}` })); } else { From 83d525aa92e3a16903ca0067fc9b7795aa0a032d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 25 Jul 2023 10:35:14 +0300 Subject: [PATCH 074/166] UI: Clear code after merge --- ui-ngx/src/app/app.component.ts | 9 ++++----- ui-ngx/src/app/shared/models/icon.models.ts | 9 ++++++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index 8f6ab35590..a3b4657120 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -30,7 +30,7 @@ import { combineLatest } from 'rxjs'; import { selectIsAuthenticated, selectIsUserLoaded } from '@core/auth/auth.selectors'; import { distinctUntilChanged, filter, map, skip } from 'rxjs/operators'; import { AuthService } from '@core/auth/auth.service'; -import { svgIcons } from '@shared/models/icon.models'; +import { svgIcons, svgIconsUrl } from '@shared/models/icon.models'; @Component({ selector: 'tb-root', @@ -65,10 +65,9 @@ export class AppComponent implements OnInit { ); } - this.matIconRegistry.addSvgIcon('windows', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/windows.svg')); - this.matIconRegistry.addSvgIcon('macos', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/macos.svg')); - this.matIconRegistry.addSvgIcon('linux', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/linux.svg')); - this.matIconRegistry.addSvgIcon('docker', this.domSanitizer.bypassSecurityTrustResourceUrl('/assets/docker.svg')); + for (const svgIcon of Object.keys(svgIconsUrl)) { + this.matIconRegistry.addSvgIcon(svgIcon, this.domSanitizer.bypassSecurityTrustResourceUrl(svgIcons[svgIcon])); + } this.storageService.testLocalStorage(); diff --git a/ui-ngx/src/app/shared/models/icon.models.ts b/ui-ngx/src/app/shared/models/icon.models.ts index 8d7d4f65bd..85c6617aa1 100644 --- a/ui-ngx/src/app/shared/models/icon.models.ts +++ b/ui-ngx/src/app/shared/models/icon.models.ts @@ -56,8 +56,15 @@ export const svgIcons: {[key: string]: string} = { '' }; +export const svgIconsUrl: { [key: string]: string } = { + windows: '/assets/windows.svg', + macos: '/assets/macos.svg', + linux: '/assets/linux.svg', + docker: '/assets/docker.svg' +}; + const svgIconNamespaces: string[] = ['mdi']; -const svgIconNames = Object.keys(svgIcons); +const svgIconNames = [...Object.keys(svgIcons), ...Object.keys(svgIconsUrl)]; export const splitIconName = (iconName: string): [string, string] => { if (!iconName) { From 08fb544b7fc29877373b2f135dacbc9ccb5fd2f9 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 25 Jul 2023 15:22:18 +0300 Subject: [PATCH 075/166] UI: Fixed alarm filter panel --- .../alarm/alarm-filter-config.component.html | 22 +++++++++++-------- .../alarm/alarm-filter-config.component.scss | 20 ++++++++++++++++- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html index c5ed4fe52a..cbbcc2ccb9 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html @@ -56,25 +56,28 @@
-
-
alarm.alarm-status-list
+
+
alarm.alarm-status-list
{{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }}
-
-
alarm.alarm-severity-list
+
+
alarm.alarm-severity-list
{{ alarmSeverityTranslationMap.get(alarmSeverityEnum[alarmSeverity]) | translate }}
-
-
alarm.alarm-type-list
- +
+
alarm.alarm-type-list
+ @@ -89,9 +92,10 @@
-
-
alarm.assignee
+
+
alarm.assignee
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss index 1c10e244b5..95f78f8fde 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss @@ -15,11 +15,24 @@ */ :host { display: block; - overflow: hidden; + overflow: scroll; max-width: 100%; .mdc-button { max-width: 100%; } + + .filters-row-mobile { + flex-direction: column; + align-items: start; + border: none; + padding: 0; + } + .filters-title-mobile { + font-size: 14px; + } + .filters-fields-width-mobile { + width: 100%; + } } :host ::ng-deep { @@ -32,4 +45,9 @@ text-overflow: ellipsis; } } + .mat-mdc-chip { + .mdc-evolution-chip__cell, .mat-mdc-chip-action, .mat-mdc-chip-action-label { + overflow: hidden; + } + } } From bab3eef8d73552d10be83012e48e12ec9fba6e00 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 26 Jul 2023 10:19:45 +0300 Subject: [PATCH 076/166] UI: Fix install command in device connectivity --- ui-ngx/src/app/app.component.ts | 2 +- .../device/device-check-connectivity-dialog.component.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index a3b4657120..67e2fd7b42 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -66,7 +66,7 @@ export class AppComponent implements OnInit { } for (const svgIcon of Object.keys(svgIconsUrl)) { - this.matIconRegistry.addSvgIcon(svgIcon, this.domSanitizer.bypassSecurityTrustResourceUrl(svgIcons[svgIcon])); + this.matIconRegistry.addSvgIcon(svgIcon, this.domSanitizer.bypassSecurityTrustResourceUrl(svgIconsUrl[svgIcon])); } this.storageService.testLocalStorage(); diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html index 11e1435119..0f9c6dc055 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html @@ -94,7 +94,7 @@
device.connectivity.install-necessary-client-tools
+ [data]='createMarkDownCommand("brew install curl")'>
device.connectivity.install-necessary-client-tools
+ [data]='createMarkDownCommand("sudo apt-get install curl")'>
downloadMqttServerCertificate(@ApiParam(value = PROTOCOL_PARAM_DESCRIPTION) - @PathVariable(PROTOCOL) String protocol) throws ThingsboardException, IOException { - String certificate = checkSslServerPemFile(protocol); + @PathVariable(PROTOCOL) String protocol) throws ThingsboardException, IOException { + checkParameter(PROTOCOL, protocol); + var pemCert = + checkNotNull(deviceConnectivityService.getPemCertFile(protocol), protocol + " pem cert file is not found!"); - ByteArrayResource cert = new ByteArrayResource(certificate.getBytes()); return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + MQTT_SSL_PEM_FILE_NAME) - .header("x-filename", MQTT_SSL_PEM_FILE_NAME) - .contentLength(cert.contentLength()) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + PEM_CERT_FILE_NAME) + .header("x-filename", PEM_CERT_FILE_NAME) + .contentLength(pemCert.contentLength()) .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(cert); + .body(pemCert); } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 5886e74ce4..86e7ec0ffe 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1004,7 +1004,7 @@ device: enabled: "${DEVICE_CONNECTIVITY_MQTTS_ENABLED:false}" host: "${DEVICE_CONNECTIVITY_MQTTS_HOST:}" port: "${DEVICE_CONNECTIVITY_MQTTS_PORT:8883}" - ssl_server_pem_path: "${DEVICE_CONNECTIVITY_MQTTS_SERVER_CHAIN_PATH:}" + pem_cert_file: "${DEVICE_CONNECTIVITY_MQTT_SSL_PEM_CERT:mqttserver.pem}" coap: enabled: "${DEVICE_CONNECTIVITY_COAP_ENABLED:true}" host: "${DEVICE_CONNECTIVITY_COAP_HOST:}" diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java index 51643fa1d4..90355d885d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java @@ -16,14 +16,14 @@ package org.thingsboard.server.dao.device; import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.core.io.Resource; import org.thingsboard.server.common.data.Device; -import java.io.IOException; import java.net.URISyntaxException; public interface DeviceConnectivityService { JsonNode findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException; - String getSslServerChain(String protocol) throws IOException; + Resource getPemCertFile(String protocol); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java index 454c795f12..033aa4e0bc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityConfiguration.java @@ -26,4 +26,9 @@ import java.util.Map; @Data public class DeviceConnectivityConfiguration { private Map connectivity; + + public boolean isEnabled(String protocol) { + var info = connectivity.get(protocol); + return info != null && info.isEnabled(); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java index fa5c61328b..b243be9995 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityInfo.java @@ -19,8 +19,8 @@ import lombok.Data; @Data public class DeviceConnectivityInfo { - private Boolean enabled; + private boolean enabled; private String host; private String port; - private String sslServerPemPath; + private String pemCertFile; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java similarity index 64% rename from dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java rename to dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java index e7bfefabbd..32a582ba07 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceСonnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java @@ -19,26 +19,25 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.FileUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.ResourceUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.dao.util.DeviceConnectivityUtil; -import java.io.File; -import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -49,17 +48,12 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.DOCKER; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.LINUX; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCoapClientCommand; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getCurlCommand; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getDockerMosquittoClientsPublishCommand; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.getMosquittoPubPublishCommand; @Service("DeviceConnectivityDaoService") @Slf4j -public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService { +public class DeviceConnectivityServiceImpl implements DeviceConnectivityService { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_DEVICE_ID = "Incorrect deviceId "; @@ -113,12 +107,13 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService } @Override - public String getSslServerChain(String protocol) throws IOException { - String mqttSslPemPath = deviceConnectivityConfiguration.getConnectivity() + public Resource getPemCertFile(String protocol) { + String certFilePath = deviceConnectivityConfiguration.getConnectivity() .get(protocol) - .getSslServerPemPath(); - if (!mqttSslPemPath.isEmpty() && ResourceUtils.resourceExists(this, mqttSslPemPath)) { - return FileUtils.readFileToString(new File(mqttSslPemPath), StandardCharsets.UTF_8); + .getPemCertFile(); + + if (StringUtils.isNotBlank(certFilePath) && ResourceUtils.resourceExists(this, certFilePath)) { + return new ClassPathResource(certFilePath); } else { return null; } @@ -134,15 +129,15 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService } private String getHttpPublishCommand(String protocol, String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { - DeviceConnectivityInfo httpProps = deviceConnectivityConfiguration.getConnectivity().get(protocol); - if (httpProps == null || !httpProps.getEnabled() || + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); + if (properties == null || !properties.isEnabled() || deviceCredentials.getCredentialsType() != DeviceCredentialsType.ACCESS_TOKEN) { return null; } - String hostName = httpProps.getHost().isEmpty() ? new URI(baseUrl).getHost() : httpProps.getHost(); - String port = httpProps.getPort().isEmpty() ? "" : ":" + httpProps.getPort(); + String hostName = getHost(baseUrl, properties); + String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); - return getCurlCommand(protocol, hostName, port, deviceCredentials); + return DeviceConnectivityUtil.getHttpPublishCommand(protocol, hostName, port, deviceCredentials); } private JsonNode getMqttTransportPublishCommands(String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { @@ -152,23 +147,31 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService private JsonNode getMqttTransportPublishCommands(String baseUrl, String topic, DeviceCredentials deviceCredentials) throws URISyntaxException { ObjectNode mqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getMqttPublishCommand(baseUrl, topic, deviceCredentials)) - .ifPresent(v -> mqttCommands.put(MQTT, v)); - List mqttsPublishCommand = getMqttsPublishCommand(baseUrl, topic, deviceCredentials); - if (mqttsPublishCommand != null){ - if (mqttsPublishCommand.size() > 1) { + if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + mqttCommands.put(MQTTS, CHECK_DOCUMENTATION); + return mqttCommands; + } + + ObjectNode dockerMqttCommands = JacksonUtil.newObjectNode(); + + if (deviceConnectivityConfiguration.isEnabled(MQTT)) { + Optional.ofNullable(getMqttPublishCommand(baseUrl, topic, deviceCredentials)). + ifPresent(v -> mqttCommands.put(MQTT, v)); + + Optional.ofNullable(getDockerMqttPublishCommand(MQTT, baseUrl, topic, deviceCredentials)) + .ifPresent(v -> dockerMqttCommands.put(MQTT, v)); + } + + if (deviceConnectivityConfiguration.isEnabled(MQTTS)) { + List mqttsPublishCommand = getMqttsPublishCommand(baseUrl, topic, deviceCredentials); + if (mqttsPublishCommand != null) { ArrayNode arrayNode = mqttCommands.putArray(MQTTS); mqttsPublishCommand.forEach(arrayNode::add); - } else { - mqttCommands.put(MQTTS, mqttsPublishCommand.get(0)); } - } - ObjectNode dockerMqttCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getDockerMqttPublishCommand(MQTT,baseUrl, topic, deviceCredentials)) - .ifPresent(v -> dockerMqttCommands.put(MQTT, v)); - Optional.ofNullable(getDockerMqttPublishCommand(MQTTS, baseUrl, topic, deviceCredentials)) - .ifPresent(v -> dockerMqttCommands.put(MQTTS, v)); + Optional.ofNullable(getDockerMqttPublishCommand(MQTTS, baseUrl, topic, deviceCredentials)) + .ifPresent(v -> dockerMqttCommands.put(MQTTS, v)); + } if (!dockerMqttCommands.isEmpty()) { mqttCommands.set(DOCKER, dockerMqttCommands); @@ -178,70 +181,81 @@ public class DeviceСonnectivityServiceImpl implements DeviceConnectivityService private String getMqttPublishCommand(String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(MQTT); - if (properties == null || !properties.getEnabled()) { - return null; - } - String mqttHost = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); + String mqttHost = getHost(baseUrl, properties); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); - return getMosquittoPubPublishCommand(MQTT, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + return DeviceConnectivityUtil.getMqttPublishCommand(MQTT, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); } private List getMqttsPublishCommand(String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { - String pubCommand; - if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { - return List.of(CHECK_DOCUMENTATION); - } else { - DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(MQTTS); - if (properties == null || !properties.getEnabled()) { - return null; - } - String mqttHost = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); - String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); - pubCommand = getMosquittoPubPublishCommand(MQTTS, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); - } + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(MQTTS); + String mqttHost = getHost(baseUrl, properties); + String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); + String pubCommand = DeviceConnectivityUtil.getMqttPublishCommand(MQTTS, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); ArrayList commands = new ArrayList<>(); if (pubCommand != null) { - commands.add("curl " + baseUrl + "/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); + commands.add(DeviceConnectivityUtil.getCurlPemCertCommand(baseUrl, MQTTS)); commands.add(pubCommand); return commands; } return null; } - private String getDockerMqttPublishCommand(String protocol, String baseUrl, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) throws URISyntaxException { DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); - if (properties == null || !properties.getEnabled()) { - return null; - } - String mqttHost = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); + String mqttHost = getHost(baseUrl, properties); String mqttPort = properties.getPort().isEmpty() ? null : properties.getPort(); - return getDockerMosquittoClientsPublishCommand(protocol, baseUrl, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); + return DeviceConnectivityUtil.getDockerMqttPublishCommand(protocol, baseUrl, mqttHost, mqttPort, deviceTelemetryTopic, deviceCredentials); } private JsonNode getCoapTransportPublishCommands(String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { ObjectNode coapCommands = JacksonUtil.newObjectNode(); - Optional.ofNullable(getCoapPublishCommand(COAP, baseUrl, deviceCredentials)) - .ifPresent(v -> coapCommands.put(COAP, v)); - Optional.ofNullable(getCoapPublishCommand(COAPS, baseUrl, deviceCredentials)) - .ifPresent(v -> coapCommands.put(COAPS, v)); + if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { + coapCommands.put(COAPS, CHECK_DOCUMENTATION); + return coapCommands; + } + + ObjectNode dockerCoapCommands = JacksonUtil.newObjectNode(); + + if (deviceConnectivityConfiguration.isEnabled(COAP)) { + Optional.ofNullable(getCoapPublishCommand(COAP, baseUrl, deviceCredentials)) + .ifPresent(v -> coapCommands.put(COAP, v)); + + Optional.ofNullable(getDockerCoapPublishCommand(COAP, baseUrl, deviceCredentials)) + .ifPresent(v -> dockerCoapCommands.put(COAP, v)); + } + + if (deviceConnectivityConfiguration.isEnabled(COAPS)) { + Optional.ofNullable(getCoapPublishCommand(COAPS, baseUrl, deviceCredentials)) + .ifPresent(v -> coapCommands.put(COAPS, v)); + + Optional.ofNullable(getDockerCoapPublishCommand(COAPS, baseUrl, deviceCredentials)) + .ifPresent(v -> dockerCoapCommands.put(COAPS, v)); + } + + if (!dockerCoapCommands.isEmpty()) { + coapCommands.set(DOCKER, dockerCoapCommands); + } return coapCommands.isEmpty() ? null : coapCommands; } private String getCoapPublishCommand(String protocol, String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { - if (COAPS.equals(protocol) && deviceCredentials.getCredentialsType() == DeviceCredentialsType.X509_CERTIFICATE) { - return CHECK_DOCUMENTATION; - } DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); - if (properties == null || !properties.getEnabled()) { - return null; - } - String hostName = properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); + String hostName = getHost(baseUrl, properties); String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); + return DeviceConnectivityUtil.getCoapPublishCommand(protocol, hostName, port, deviceCredentials); + } + + private String getDockerCoapPublishCommand(String protocol, String baseUrl, DeviceCredentials deviceCredentials) throws URISyntaxException { + DeviceConnectivityInfo properties = deviceConnectivityConfiguration.getConnectivity().get(protocol); + String host = getHost(baseUrl, properties); + String port = properties.getPort().isEmpty() ? "" : ":" + properties.getPort(); + return DeviceConnectivityUtil.getDockerCoapPublishCommand(protocol, host, port, deviceCredentials); + } - return getCoapClientCommand(protocol, hostName, port, deviceCredentials); + private String getHost(String baseUrl, DeviceConnectivityInfo properties) throws URISyntaxException { + return properties.getHost().isEmpty() ? new URI(baseUrl).getHost() : properties.getHost(); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java index dad405b093..1d20c62d70 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java @@ -30,19 +30,22 @@ public class DeviceConnectivityUtil { public static final String MQTTS = "mqtts"; public static final String COAP = "coap"; public static final String COAPS = "coaps"; - public static final String MQTT_SSL_PEM_FILE_NAME = "tb-server-chain.pem"; + public static final String PEM_CERT_FILE_NAME = "tb-server-chain.pem"; public static final String CHECK_DOCUMENTATION = "Check documentation"; public static final String JSON_EXAMPLE_PAYLOAD = "\"{temperature:25}\""; + public static final String DOCKER_RUN = "docker run --rm -it "; + public static final String MQTT_IMAGE = "thingsboard/mosquitto-clients "; + public static final String COAP_IMAGE = "thingsboard/coap-clients "; - public static String getCurlCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { + public static String getHttpPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { return String.format("curl -v -X POST %s://%s%s/api/v1/%s/telemetry --header Content-Type:application/json --data " + JSON_EXAMPLE_PAYLOAD, protocol, host, port, deviceCredentials.getCredentialsId()); } - public static String getMosquittoPubPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + public static String getMqttPublishCommand(String protocol, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { StringBuilder command = new StringBuilder("mosquitto_pub -d -q 1"); if (MQTTS.equals(protocol)) { - command.append(" --cafile tmp/" + MQTT_SSL_PEM_FILE_NAME); + command.append(" --cafile ").append(PEM_CERT_FILE_NAME); } command.append(" -h ").append(host).append(port == null ? "" : " -p " + port); command.append(" -t ").append(deviceTelemetryTopic); @@ -75,50 +78,34 @@ public class DeviceConnectivityUtil { return command.toString(); } - public static String getDockerMosquittoClientsPublishCommand(String protocol, String baseUrl, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { - StringBuilder command = new StringBuilder("docker run -it --rm thingsboard/mosquitto-clients "); - if (MQTTS.equals(protocol)) { - command.append("/bin/sh -c \"curl -o /tmp/tb-server-chain.pem ").append(baseUrl).append("/api/device-connectivity/mqtts/certificate/download && "); - } - command.append("pub"); - if (MQTTS.equals(protocol)) { - command.append(" --cafile tmp/" + MQTT_SSL_PEM_FILE_NAME); - } - command.append(" -h ").append(host).append(port == null ? "" : " -p " + port); - command.append(" -t ").append(deviceTelemetryTopic); + public static String getDockerMqttPublishCommand(String protocol, String baseUrl, String host, String port, String deviceTelemetryTopic, DeviceCredentials deviceCredentials) { + String mqttCommand = getMqttPublishCommand(protocol, host, port, deviceTelemetryTopic, deviceCredentials); - switch (deviceCredentials.getCredentialsType()) { - case ACCESS_TOKEN: - command.append(" -u ").append(deviceCredentials.getCredentialsId()); - break; - case MQTT_BASIC: - BasicMqttCredentials credentials = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), - BasicMqttCredentials.class); - if (credentials != null) { - if (credentials.getClientId() != null) { - command.append(" -i ").append(credentials.getClientId()); - } - if (credentials.getUserName() != null) { - command.append(" -u ").append(credentials.getUserName()); - } - if (credentials.getPassword() != null) { - command.append(" -P ").append(credentials.getPassword()); - } - } else { - return null; - } - break; - default: - return null; + if (mqttCommand == null) { + return null; } - command.append(" -m " + JSON_EXAMPLE_PAYLOAD); + + StringBuilder mqttDockerCommand = new StringBuilder(); + mqttDockerCommand.append(DOCKER_RUN).append(MQTT_IMAGE); + if (MQTTS.equals(protocol)) { - command.append("\""); + mqttDockerCommand.append("/bin/sh -c \"") + .append(getCurlPemCertCommand(baseUrl, protocol)) + .append(" && ") + .append(mqttCommand) + .append("\""); + } else { + mqttDockerCommand.append(mqttCommand); } - return command.toString(); + + return mqttDockerCommand.toString(); } - public static String getCoapClientCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { + public static String getCurlPemCertCommand(String baseUrl, String protocol) { + return String.format("curl -f -S -o %s %s/api/device-connectivity/%s/certificate/download", PEM_CERT_FILE_NAME, baseUrl, protocol); + } + + public static String getCoapPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { switch (deviceCredentials.getCredentialsType()) { case ACCESS_TOKEN: String client = COAPS.equals(protocol) ? "coap-client-openssl" : "coap-client"; @@ -128,4 +115,9 @@ public class DeviceConnectivityUtil { return null; } } + + public static String getDockerCoapPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { + String coapCommand = getCoapPublishCommand(protocol, host, port, deviceCredentials); + return coapCommand != null ? String.format("%s%s%s", DOCKER_RUN, COAP_IMAGE, coapCommand) : null; + } } From b8253b139b9c531b12bac74433665dc273ca88ab Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 26 Jul 2023 10:23:32 +0200 Subject: [PATCH 078/166] fixed tests --- .../DeviceConnectivityControllerTest.java | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index 5e40f3e993..7427ec1fc1 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -57,10 +57,8 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.DOCKER; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.LINUX; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTT; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.MQTTS; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.WINDOWS; @TestPropertySource(properties = { "device.connectivity.https.enabled=true", @@ -157,7 +155,8 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); JsonNode commands = - doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); DeviceCredentials credentials = doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); @@ -176,24 +175,24 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t v1/devices/me/telemetry " + "-u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl http://localhost:80/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); - assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tmp/tb-server-chain.pem -h localhost -p 8883 " + - "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); + assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl -f -S -o tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download"); + assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 " + + "-t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); - assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + " -p 1883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients " + - "/bin/sh -c \"curl -o /tmp/tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + - "pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"\"", + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " + + "/bin/sh -c \"curl -f -S -o tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + + "mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u %s -m \"{temperature:25}\"\"", credentials.getCredentialsId())); JsonNode linuxCoapCommands = commands.get(COAP); assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry " + - "-t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + "-t json -e \"{temperature:25}\"", credentials.getCredentialsId())); assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry" + - " -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + " -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } @Test @@ -207,23 +206,24 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); JsonNode commands = - doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); assertThat(commands).hasSize(1); JsonNode mqttCommands = commands.get(MQTT); assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + - "-u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl http://localhost:80/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); - assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tmp/tb-server-chain.pem -h localhost -p 8883 " + + "-u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); + assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl -f -S -o tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download"); + assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 " + "-t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); - assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + " -p 1883 -t %s -u %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients " + - "/bin/sh -c \"curl -o /tmp/tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + - "pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"\"", + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " + + "/bin/sh -c \"curl -f -S -o tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + + "mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -u %s -m \"{temperature:25}\"\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); } @@ -250,23 +250,24 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); JsonNode commands = - doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId() , new TypeReference<>() {}); + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); assertThat(commands).hasSize(1); JsonNode mqttCommands = commands.get(MQTT); assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h localhost -p 1883 -t %s " + "-i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl http://localhost:80/api/device-connectivity/mqtts/certificate/download -o /tmp/tb-server-chain.pem"); - assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tmp/tb-server-chain.pem -h localhost -p 8883 " + + assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl -f -S -o tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download"); + assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 " + "-t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); - assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients pub -h localhost" + + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + " -p 1883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run -it --rm thingsboard/mosquitto-clients " + - "/bin/sh -c \"curl -o /tmp/tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + - "pub --cafile tmp/tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"\"", + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " + + "/bin/sh -c \"curl -f -S -o tb-server-chain.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + + "mosquitto_pub -d -q 1 --cafile tb-server-chain.pem -h localhost -p 8883 -t %s -i %s -u %s -P %s -m \"{temperature:25}\"\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); } @@ -286,9 +287,10 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); JsonNode commands = - doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); assertThat(commands).hasSize(1); - assertThat(commands.get(MQTT).get(MQTTS).get(0).asText()).isEqualTo(CHECK_DOCUMENTATION); + assertThat(commands.get(MQTT).get(MQTTS).asText()).isEqualTo(CHECK_DOCUMENTATION); assertThat(commands.get(MQTT).get(DOCKER)).isNull(); } @@ -303,7 +305,8 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); JsonNode commands = - doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); assertThat(commands).hasSize(1); JsonNode linuxCommands = commands.get(COAP); @@ -329,7 +332,8 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); JsonNode commands = - doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() {}); + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); assertThat(commands).hasSize(1); assertThat(commands.get(COAP).get(COAPS).asText()).isEqualTo(CHECK_DOCUMENTATION); } From 329a24c019cba7f2df062306d0b95ea3311d4f63 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 26 Jul 2023 11:05:50 +0200 Subject: [PATCH 079/166] added sparkplug --- .../DeviceConnectivityControllerTest.java | 4 ++-- .../dao/device/DeviceConnectivityServiceImpl.java | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index 7427ec1fc1..36a4365544 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -295,7 +295,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { } @Test - public void testFetchPublishTelemetryCommandsForСoapDevice() throws Exception { + public void testFetchPublishTelemetryCommandsForCoapDevice() throws Exception { Device device = new Device(); device.setName("My device"); device.setDeviceProfileId(coapDeviceProfileId); @@ -317,7 +317,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { } @Test - public void testFetchPublishTelemetryCommandsForСoapDeviceWithX509Creds() throws Exception { + public void testFetchPublishTelemetryCommandsForCoapDeviceWithX509Creds() throws Exception { Device device = new Device(); device.setName("My device"); device.setDeviceProfileId(coapDeviceProfileId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java index 32a582ba07..c06103d8f3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java @@ -91,10 +91,17 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService case MQTT: MqttDeviceProfileTransportConfiguration transportConfiguration = (MqttDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); - String topicName = transportConfiguration.getDeviceTelemetryTopic(); - - Optional.ofNullable(getMqttTransportPublishCommands(baseUrl, topicName, creds)) - .ifPresent(v -> commands.set(MQTT, v)); + //TODO: add sparkplug command with emulator (check SSL) + if (transportConfiguration.isSparkplug()) { + ObjectNode sparkplug = JacksonUtil.newObjectNode(); + sparkplug.put("sparkplug", CHECK_DOCUMENTATION); + commands.set(MQTT, sparkplug); + } else { + String topicName = transportConfiguration.getDeviceTelemetryTopic(); + + Optional.ofNullable(getMqttTransportPublishCommands(baseUrl, topicName, creds)) + .ifPresent(v -> commands.set(MQTT, v)); + } break; case COAP: Optional.ofNullable(getCoapTransportPublishCommands(baseUrl, creds)) From 7e27c5b6833a725c4d0c4e524b3c483d95001451 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 13 Jun 2023 18:17:25 +0200 Subject: [PATCH 080/166] mqtt-client: messages processing moved from netty event loop pool and to the handlerExecutor to make netty handlers non-blocking --- .../msa/connectivity/MqttClientTest.java | 16 ++- .../connectivity/MqttGatewayClientTest.java | 16 ++- netty-mqtt/pom.xml | 4 + .../thingsboard/mqtt/MqttChannelHandler.java | 113 +++++++++++++----- .../java/org/thingsboard/mqtt/MqttClient.java | 7 +- .../org/thingsboard/mqtt/MqttClientImpl.java | 15 ++- .../thingsboard/mqtt/MqttSubscription.java | 2 +- .../mqtt/integration/MqttIntegrationTest.java | 16 ++- .../rule/engine/mqtt/TbMqttNode.java | 2 +- 9 files changed, 153 insertions(+), 38 deletions(-) diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java index 893c8b565c..96e57549a8 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java @@ -28,6 +28,7 @@ import lombok.extern.slf4j.Slf4j; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import org.thingsboard.common.util.AbstractListeningExecutor; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.mqtt.MqttClient; import org.thingsboard.mqtt.MqttClientConfig; @@ -74,8 +75,18 @@ import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultDevi public class MqttClientTest extends AbstractContainerTest { private Device device; + AbstractListeningExecutor handlerExecutor; + @BeforeMethod public void setUp() throws Exception { + this.handlerExecutor = new AbstractListeningExecutor() { + @Override + protected int getThreadPollSize() { + return 4; + } + }; + handlerExecutor.init(); + testRestClient.login("tenant@thingsboard.org", "tenant"); device = testRestClient.postDevice("", defaultDevicePrototype("http_")); } @@ -83,6 +94,9 @@ public class MqttClientTest extends AbstractContainerTest { @AfterMethod public void tearDown() { testRestClient.deleteDeviceIfExists(device.getId()); + if (handlerExecutor != null) { + handlerExecutor.destroy(); + } } @Test public void telemetryUpload() throws Exception { @@ -465,7 +479,7 @@ public class MqttClientTest extends AbstractContainerTest { MqttClientConfig clientConfig = new MqttClientConfig(); clientConfig.setClientId("MQTT client from test"); clientConfig.setUsername(username); - MqttClient mqttClient = MqttClient.create(clientConfig, listener); + MqttClient mqttClient = MqttClient.create(clientConfig, listener, handlerExecutor); mqttClient.connect("localhost", 1883).get(); return mqttClient; } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java index a038d4cf50..8cddb69fa3 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java @@ -32,6 +32,7 @@ import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import org.thingsboard.common.util.AbstractListeningExecutor; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.mqtt.MqttClient; @@ -76,8 +77,18 @@ public class MqttGatewayClientTest extends AbstractContainerTest { private MqttMessageListener listener; private JsonParser jsonParser = new JsonParser(); + AbstractListeningExecutor handlerExecutor; + @BeforeMethod public void createGateway() throws Exception { + this.handlerExecutor = new AbstractListeningExecutor() { + @Override + protected int getThreadPollSize() { + return 4; + } + }; + handlerExecutor.init(); + testRestClient.login("tenant@thingsboard.org", "tenant"); gatewayDevice = testRestClient.postDevice("", defaultGatewayPrototype()); DeviceCredentials gatewayDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(gatewayDevice.getId()); @@ -94,6 +105,9 @@ public class MqttGatewayClientTest extends AbstractContainerTest { this.listener = null; this.mqttClient = null; this.createdDevice = null; + if (handlerExecutor != null) { + handlerExecutor.destroy(); + } } @Test @@ -407,7 +421,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { MqttClientConfig clientConfig = new MqttClientConfig(); clientConfig.setClientId("MQTT client from test"); clientConfig.setUsername(deviceCredentials.getCredentialsId()); - MqttClient mqttClient = MqttClient.create(clientConfig, listener); + MqttClient mqttClient = MqttClient.create(clientConfig, listener, handlerExecutor); mqttClient.connect("localhost", 1883).get(); return mqttClient; } diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 60883f4b34..400b486e18 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -35,6 +35,10 @@ + + org.thingsboard.common + util + io.netty netty-codec-mqtt diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java index e243f66633..6b3a4e009e 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java @@ -16,6 +16,10 @@ package org.thingsboard.mqtt; import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; @@ -34,8 +38,15 @@ import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttSubAckMessage; import io.netty.handler.codec.mqtt.MqttUnsubAckMessage; import io.netty.util.CharsetUtil; +import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.Promise; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.Nullable; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +@Slf4j final class MqttChannelHandler extends SimpleChannelInboundHandler { private final MqttClientImpl client; @@ -110,27 +121,48 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler super.channelInactive(ctx); } - private void invokeHandlersForIncomingPublish(MqttPublishMessage message) { - boolean handlerInvoked = false; - for (MqttSubscription subscription : ImmutableSet.copyOf(this.client.getSubscriptions().values())) { - if (subscription.matches(message.variableHeader().topicName())) { - if (subscription.isOnce() && subscription.isCalled()) { - continue; - } - message.payload().markReaderIndex(); - subscription.setCalled(true); - subscription.getHandler().onMessage(message.variableHeader().topicName(), message.payload()); - if (subscription.isOnce()) { - this.client.off(subscription.getTopic(), subscription.getHandler()); + ListenableFuture invokeHandlersForIncomingPublish(MqttPublishMessage message) { + var future = Futures.immediateVoidFuture(); + var handlerInvoked = new AtomicBoolean(); + try { + for (MqttSubscription subscription : ImmutableSet.copyOf(this.client.getSubscriptions().values())) { + if (subscription.matches(message.variableHeader().topicName())) { + future = Futures.transform(future, x -> { + if (subscription.isOnce() && subscription.isCalled()) { + return null; + } + message.payload().markReaderIndex(); + subscription.setCalled(true); + subscription.getHandler().onMessage(message.variableHeader().topicName(), message.payload()); + if (subscription.isOnce()) { + this.client.off(subscription.getTopic(), subscription.getHandler()); + } + message.payload().resetReaderIndex(); + handlerInvoked.set(true); + return null; + }, client.getHandlerExecutor()); } - message.payload().resetReaderIndex(); - handlerInvoked = true; } + future = Futures.transform(future, x -> { + if (!handlerInvoked.get() && client.getDefaultHandler() != null) { + client.getDefaultHandler().onMessage(message.variableHeader().topicName(), message.payload()); + } + return null; + }, client.getHandlerExecutor()); + } finally { + Futures.addCallback(future, new FutureCallback<>() { + @Override + public void onSuccess(@Nullable Void result) { + message.payload().release(); + } + + @Override + public void onFailure(Throwable t) { + message.payload().release(); + } + }, MoreExecutors.directExecutor()); } - if (!handlerInvoked && client.getDefaultHandler() != null) { - client.getDefaultHandler().onMessage(message.variableHeader().topicName(), message.payload()); - } - message.payload().release(); + return future; } private void handleConack(Channel channel, MqttConnAckMessage message) { @@ -197,11 +229,13 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler break; case AT_LEAST_ONCE: - invokeHandlersForIncomingPublish(message); + var future = invokeHandlersForIncomingPublish(message); if (message.variableHeader().packetId() != -1) { - MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0); - MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(message.variableHeader().packetId()); - channel.writeAndFlush(new MqttPubAckMessage(fixedHeader, variableHeader)); + future.addListener(() -> { + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0); + MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(message.variableHeader().packetId()); + channel.writeAndFlush(new MqttPubAckMessage(fixedHeader, variableHeader)); + }, MoreExecutors.directExecutor()); } break; @@ -256,14 +290,20 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler } private void handlePubrel(Channel channel, MqttMessage message) { + var future = Futures.immediateVoidFuture(); if (this.client.getQos2PendingIncomingPublishes().containsKey(((MqttMessageIdVariableHeader) message.variableHeader()).messageId())) { MqttIncomingQos2Publish incomingQos2Publish = this.client.getQos2PendingIncomingPublishes().get(((MqttMessageIdVariableHeader) message.variableHeader()).messageId()); - this.invokeHandlersForIncomingPublish(incomingQos2Publish.getIncomingPublish()); - this.client.getQos2PendingIncomingPublishes().remove(incomingQos2Publish.getIncomingPublish().variableHeader().packetId()); + future = invokeHandlersForIncomingPublish(incomingQos2Publish.getIncomingPublish()); + future = Futures.transform(future, x -> { + this.client.getQos2PendingIncomingPublishes().remove(incomingQos2Publish.getIncomingPublish().variableHeader().packetId()); + return null; + }, MoreExecutors.directExecutor()); } - MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0); - MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(((MqttMessageIdVariableHeader) message.variableHeader()).messageId()); - channel.writeAndFlush(new MqttMessage(fixedHeader, variableHeader)); + future.addListener(() -> { + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0); + MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(((MqttMessageIdVariableHeader) message.variableHeader()).messageId()); + channel.writeAndFlush(new MqttMessage(fixedHeader, variableHeader)); + }, MoreExecutors.directExecutor()); } private void handlePubcomp(MqttMessage message) { @@ -274,4 +314,23 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler pendingPublish.getPayload().release(); pendingPublish.onPubcompReceived(); } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + try { + if (cause instanceof IOException) { + if (log.isDebugEnabled()) { + log.debug("[{}][{}][{}] IOException: ", client.getClientConfig().getClientId(), client.getClientConfig().getUsername() , ctx.channel().remoteAddress(), + cause); + } else if (log.isInfoEnabled()) { + log.info("[{}][{}][{}] IOException: {}", client.getClientConfig().getClientId(), client.getClientConfig().getUsername() , ctx.channel().remoteAddress(), + cause.getMessage()); + } + } else { + log.warn("exceptionCaught", cause); + } + } finally { + ReferenceCountUtil.release(cause); + } + } } diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClient.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClient.java index 2fe179de31..536a76119f 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClient.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClient.java @@ -21,6 +21,7 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.util.concurrent.Future; +import org.thingsboard.common.util.ListeningExecutor; public interface MqttClient { @@ -71,6 +72,8 @@ public interface MqttClient { */ void setEventLoop(EventLoopGroup eventLoop); + ListeningExecutor getHandlerExecutor(); + /** * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler * @@ -180,8 +183,8 @@ public interface MqttClient { * @param config The config object to use while looking for settings * @param defaultHandler The handler for incoming messages that do not match any topic subscriptions */ - static MqttClient create(MqttClientConfig config, MqttHandler defaultHandler){ - return new MqttClientImpl(config, defaultHandler); + static MqttClient create(MqttClientConfig config, MqttHandler defaultHandler, ListeningExecutor handlerExecutor){ + return new MqttClientImpl(config, defaultHandler, handlerExecutor); } /** diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index f38a790be7..63d65a1cc2 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java @@ -46,6 +46,7 @@ import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Promise; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.ListeningExecutor; import java.util.Collections; import java.util.HashSet; @@ -88,13 +89,13 @@ final class MqttClientImpl implements MqttClient { private int port; private MqttClientCallback callback; + private final ListeningExecutor handlerExecutor; /** * Construct the MqttClientImpl with default config */ - public MqttClientImpl(MqttHandler defaultHandler) { - this.clientConfig = new MqttClientConfig(); - this.defaultHandler = defaultHandler; + public MqttClientImpl(MqttHandler defaultHandler, ListeningExecutor handlerExecutor) { + this(new MqttClientConfig(), defaultHandler, handlerExecutor); } /** @@ -103,9 +104,10 @@ final class MqttClientImpl implements MqttClient { * * @param clientConfig The config object to use while looking for settings */ - public MqttClientImpl(MqttClientConfig clientConfig, MqttHandler defaultHandler) { + public MqttClientImpl(MqttClientConfig clientConfig, MqttHandler defaultHandler, ListeningExecutor handlerExecutor) { this.clientConfig = clientConfig; this.defaultHandler = defaultHandler; + this.handlerExecutor = handlerExecutor; } /** @@ -227,6 +229,11 @@ final class MqttClientImpl implements MqttClient { this.eventLoop = eventLoop; } + @Override + public ListeningExecutor getHandlerExecutor() { + return this.handlerExecutor; + } + /** * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler * diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttSubscription.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttSubscription.java index 6c4abb4c5c..c4bc9e38c1 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttSubscription.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttSubscription.java @@ -25,7 +25,7 @@ final class MqttSubscription { private final boolean once; - private boolean called; + private volatile boolean called; MqttSubscription(String topic, MqttHandler handler, boolean once) { if (topic == null) { diff --git a/netty-mqtt/src/test/java/org/thingsboard/mqtt/integration/MqttIntegrationTest.java b/netty-mqtt/src/test/java/org/thingsboard/mqtt/integration/MqttIntegrationTest.java index cb1b6b81fe..f39ca01110 100644 --- a/netty-mqtt/src/test/java/org/thingsboard/mqtt/integration/MqttIntegrationTest.java +++ b/netty-mqtt/src/test/java/org/thingsboard/mqtt/integration/MqttIntegrationTest.java @@ -26,6 +26,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.thingsboard.common.util.AbstractListeningExecutor; import org.thingsboard.mqtt.MqttClient; import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.mqtt.MqttConnectResult; @@ -49,8 +50,18 @@ public class MqttIntegrationTest { MqttClient mqttClient; + AbstractListeningExecutor handlerExecutor; + @Before public void init() throws Exception { + this.handlerExecutor = new AbstractListeningExecutor() { + @Override + protected int getThreadPollSize() { + return 4; + } + }; + handlerExecutor.init(); + this.eventLoopGroup = new NioEventLoopGroup(); this.mqttServer = new MqttServer(); @@ -68,6 +79,9 @@ public class MqttIntegrationTest { if (this.eventLoopGroup != null) { this.eventLoopGroup.shutdownGracefully(0, 0, TimeUnit.MILLISECONDS); } + if (this.handlerExecutor != null) { + this.handlerExecutor.destroy(); + } } @Test @@ -110,7 +124,7 @@ public class MqttIntegrationTest { MqttClientConfig config = new MqttClientConfig(); config.setTimeoutSeconds(KEEPALIVE_TIMEOUT_SECONDS); config.setReconnectDelay(RECONNECT_DELAY_SECONDS); - MqttClient client = MqttClient.create(config, null); + MqttClient client = MqttClient.create(config, null, handlerExecutor); client.setEventLoop(this.eventLoopGroup); Future connectFuture = client.connect(MQTT_HOST, this.mqttServer.getMqttPort()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java index 121b9fb756..49b31cb33c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java @@ -114,7 +114,7 @@ public class TbMqttNode extends TbAbstractExternalNode { config.setCleanSession(this.mqttNodeConfiguration.isCleanSession()); prepareMqttClientConfig(config); - MqttClient client = MqttClient.create(config, null); + MqttClient client = MqttClient.create(config, null, ctx.getExternalCallExecutor()); client.setEventLoop(ctx.getSharedEventLoop()); Future connectFuture = client.connect(this.mqttNodeConfiguration.getHost(), this.mqttNodeConfiguration.getPort()); MqttConnectResult result; From d74e0c45df8442e709928c3c04efe36442782a07 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 20 Jun 2023 14:26:30 +0200 Subject: [PATCH 081/166] MqttHandler - processAsync (required for AbstractMqttIntegration) --- .../thingsboard/server/msa/connectivity/MqttClientTest.java | 4 +++- .../server/msa/connectivity/MqttGatewayClientTest.java | 4 +++- .../src/main/java/org/thingsboard/mqtt/MqttHandler.java | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java index 96e57549a8..940bd6777e 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.msa.connectivity; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; @@ -493,9 +494,10 @@ public class MqttClientTest extends AbstractContainerTest { } @Override - public void onMessage(String topic, ByteBuf message) { + public ListenableFuture onMessage(String topic, ByteBuf message) { log.info("MQTT message [{}], topic [{}]", message.toString(StandardCharsets.UTF_8), topic); events.add(new MqttEvent(topic, message.toString(StandardCharsets.UTF_8))); + return Futures.immediateVoidFuture(); } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java index 8cddb69fa3..de11df2623 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.msa.connectivity; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; @@ -435,9 +436,10 @@ public class MqttGatewayClientTest extends AbstractContainerTest { } @Override - public void onMessage(String topic, ByteBuf message) { + public ListenableFuture onMessage(String topic, ByteBuf message) { log.info("MQTT message [{}], topic [{}]", message.toString(StandardCharsets.UTF_8), topic); events.add(new MqttEvent(topic, message.toString(StandardCharsets.UTF_8))); + return Futures.immediateVoidFuture(); } } diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttHandler.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttHandler.java index 0ec03ff04b..21c07a17cd 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttHandler.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttHandler.java @@ -15,9 +15,10 @@ */ package org.thingsboard.mqtt; +import com.google.common.util.concurrent.ListenableFuture; import io.netty.buffer.ByteBuf; public interface MqttHandler { - void onMessage(String topic, ByteBuf payload); + ListenableFuture onMessage(String topic, ByteBuf payload); } From 5e83b2b903d9a9be0d281a55c13fbe18f0f43698 Mon Sep 17 00:00:00 2001 From: rusikv Date: Wed, 26 Jul 2023 14:26:00 +0300 Subject: [PATCH 082/166] Add double quotes to highlight 'remove other entities' confirm phrase in version control dialog --- ui-ngx/src/assets/locale/locale.constant-ca_ES.json | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- ui-ngx/src/assets/locale/locale.constant-es_ES.json | 2 +- ui-ngx/src/assets/locale/locale.constant-zh_CN.json | 2 +- ui-ngx/src/assets/locale/locale.constant-zh_TW.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json index 349d13da2c..dbb54c2bea 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json @@ -4481,7 +4481,7 @@ "created": "{{created}} creades", "updated": "{{updated}} actualitzades", "deleted": "{{deleted}} esborrades", - "remove-other-entities-confirm-text": "Atenció! Aquesta acció esborrarà permanentment todas les entitats actuals
no presents a la versió a restaurar.

Escriu eliminar altres entitats per confirmar.", + "remove-other-entities-confirm-text": "Atenció! Aquesta acció esborrarà permanentment todas les entitats actuals
no presents a la versió a restaurar.

Escriu \"remove other entities\" per confirmar.", "auto-commit-to-branch": "autopublicar a la branca {{ branch }}", "default-create-entity-version-name": "{{entityName}} actualizació", "sync-strategy-merge-hint": "Crea o actualitza les entitats seleccionades al repositori. Les altres entitats no seran modificades.", diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 0f96a5f1dc..094ff2ed52 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4429,7 +4429,7 @@ "created": "{{created}} created", "updated": "{{updated}} updated", "deleted": "{{deleted}} deleted", - "remove-other-entities-confirm-text": "Be careful! This will permanently delete all current entities
not present in the version you want to restore.

Please type remove other entities to confirm.", + "remove-other-entities-confirm-text": "Be careful! This will permanently delete all current entities
not present in the version you want to restore.

Please type \"remove other entities\" to confirm.", "auto-commit-to-branch": "auto-commit to {{ branch }} branch", "default-create-entity-version-name": "{{entityName}} update", "sync-strategy-merge-hint": "Creates or updates selected entities in the repository. All other repository entities are not modified.", diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index 6518e03f58..a2abda3b1e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -3907,7 +3907,7 @@ "created": "{{created}} creadas", "updated": "{{updated}} actualizadas", "deleted": "{{deleted}} borradas", - "remove-other-entities-confirm-text": "Atención! Esta acción borrará permanentemente todas las entidades actuales
no presentes en la versión a restaurar.

Escribe remove other entities para confirmar.", + "remove-other-entities-confirm-text": "Atención! Esta acción borrará permanentemente todas las entidades actuales
no presentes en la versión a restaurar.

Escribe \"remove other entities\" para confirmar.", "auto-commit-to-branch": "auto-publicar a la rama {{ branch }}", "default-create-entity-version-name": "{{entityName}} actualización", "sync-strategy-merge-hint": "Crea o actualiza las entidades seleccionadas en el repositorio. Las demás entidades no serán modificadas.", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index b39e10e46c..401014aaca 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -3488,7 +3488,7 @@ "created": "{{created}} 创建", "updated": "{{updated}} 更新", "deleted": "{{deleted}} 删除", - "remove-other-entities-confirm-text": "请注意!在还原版本中不存在的当前实体
将被永久 删除

请输入 remove other entities 进行确认。", + "remove-other-entities-confirm-text": "请注意!在还原版本中不存在的当前实体
将被永久 删除

请输入 \"remove other entities\" 进行确认。", "auto-commit-to-branch": "自动提交到 {{ branch }} 分支", "default-create-entity-version-name": "{{entityName}} 更新", "sync-strategy-merge-hint": "创建或更新选定的实体,仓库其他实体均不修改。", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json index f2cce81824..0caea01b35 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json @@ -3338,7 +3338,7 @@ "created": "{{created}}已創建", "updated": "{{updated}}已更新", "deleted": "{{deleted}} 已刪除", - "remove-other-entities-confirm-text": "小心!這將永久刪除您要恢復的版本中不存在的所有當前實體。請鍵入刪除其他實體進行確認。", + "remove-other-entities-confirm-text": "小心!這將永久刪除所有在您要恢復的版本中不存在的當前實體。請輸入 \"remove other entities\" 進行確認。", "auto-commit-to-branch": "自動提交到{{ branch }}分支", "default-create-entity-version-name": "{{entityName}} 更新", "sync-strategy-merge-hint": "在存儲庫中創建或更新選定實體。所有其他存儲實體都不會被修改。", From c3e9ab59918f04c5eb9d79df47948c187d09e7cf Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 26 Jul 2023 20:38:22 +0200 Subject: [PATCH 083/166] TbKafkaProducerTemplate will add headers for each message when log level: DEBUG - producerId and thread name; TRACE - stacktrace first 10-2=8 lines --- .../queue/kafka/TbKafkaProducerTemplate.java | 28 +++++++++- .../kafka/TbKafkaProducerTemplateTest.java | 54 +++++++++++++++++++ .../queue/src/test/resources/logback-test.xml | 20 +++++++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 common/queue/src/test/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplateTest.java create mode 100644 common/queue/src/test/resources/logback-test.xml diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java index 15c2f04d17..7c1c28b9f5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java @@ -30,6 +30,9 @@ import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsg; import org.thingsboard.server.queue.TbQueueProducer; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -53,10 +56,14 @@ public class TbKafkaProducerTemplate implements TbQueuePro private final Set topics; + @Getter + private final String clientId; + @Builder private TbKafkaProducerTemplate(TbKafkaSettings settings, String defaultTopic, String clientId, TbQueueAdmin admin) { Properties props = settings.toProducerProps(); + this.clientId = Objects.requireNonNull(clientId, "Kafka producer client.id is null"); if (!StringUtils.isEmpty(clientId)) { props.put(ProducerConfig.CLIENT_ID_CONFIG, clientId); } @@ -72,6 +79,24 @@ public class TbKafkaProducerTemplate implements TbQueuePro public void init() { } + void addAnalyticHeaders(List
headers) { + try { + if (log.isDebugEnabled()) { + headers.add(new RecordHeader("_producerId", getClientId().getBytes(StandardCharsets.UTF_8))); + headers.add(new RecordHeader("_threadName", Thread.currentThread().getName().getBytes(StandardCharsets.UTF_8))); + } + if (log.isTraceEnabled()) { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + int maxlevel = Math.min(stackTrace.length, 10); + for (int i = 2; i < maxlevel; i++) { // ignore two levels: getStackTrace and addAnalyticHeaders + headers.add(new RecordHeader("_stackTrace" + i, stackTrace[i].toString().getBytes(StandardCharsets.UTF_8))); + } + } + } catch (Throwable t) { + log.debug("Failed to add analytic header in Kafka producer {}", getClientId(), t); + } + } + @Override public void send(TopicPartitionInfo tpi, T msg, TbQueueCallback callback) { try { @@ -79,7 +104,8 @@ public class TbKafkaProducerTemplate implements TbQueuePro String key = msg.getKey().toString(); byte[] data = msg.getData(); ProducerRecord record; - Iterable
headers = msg.getHeaders().getData().entrySet().stream().map(e -> new RecordHeader(e.getKey(), e.getValue())).collect(Collectors.toList()); + List
headers = msg.getHeaders().getData().entrySet().stream().map(e -> new RecordHeader(e.getKey(), e.getValue())).collect(Collectors.toList()); + addAnalyticHeaders(headers); record = new ProducerRecord<>(tpi.getFullTopicName(), null, key, data, headers); producer.send(record, (metadata, exception) -> { if (exception == null) { diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplateTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplateTest.java new file mode 100644 index 0000000000..bfd3c4a6dc --- /dev/null +++ b/common/queue/src/test/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplateTest.java @@ -0,0 +1,54 @@ +/** + * 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.queue.kafka; + +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.common.header.Header; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.thingsboard.server.queue.TbQueueMsg; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.willCallRealMethod; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.mock; + +@Slf4j +class TbKafkaProducerTemplateTest { + + TbKafkaProducerTemplate producerTemplate; + + @BeforeEach + void setUp() { + producerTemplate = mock(TbKafkaProducerTemplate.class); + willCallRealMethod().given(producerTemplate).addAnalyticHeaders(any()); + willReturn("tb-core-to-core-notifications-tb-core-3").given(producerTemplate).getClientId(); + } + + @Test + void testAddAnalyticHeaders() { + List
headers = new ArrayList<>(); + producerTemplate.addAnalyticHeaders(headers); + assertThat(headers).isNotEmpty(); + headers.forEach(r -> log.info("RecordHeader key [{}] value [{}]", r.key(), new String(r.value(), StandardCharsets.UTF_8))); + } + +} diff --git a/common/queue/src/test/resources/logback-test.xml b/common/queue/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..f7053313d4 --- /dev/null +++ b/common/queue/src/test/resources/logback-test.xml @@ -0,0 +1,20 @@ + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + From 2d4fbd6833a65df07900f2249ad921abc77075a4 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 27 Jul 2023 14:48:53 +0300 Subject: [PATCH 084/166] added checkMsgType util method to TbMsg & resolved other review comments --- .../queue/DefaultTbClusterService.java | 4 +- .../state/DefaultDeviceStateService.java | 14 ++--- .../server/common/data/DataConstants.java | 44 +++++++++++++++ .../server/common/data/StringUtils.java | 6 +-- .../thingsboard/server/common/msg/TbMsg.java | 22 ++++++-- .../common/msg/session/SessionMsgType.java | 54 +++++++++++++++++++ .../engine/action/TbAbstractAlarmNode.java | 6 +-- .../TbCopyAttributesToEntityViewNode.java | 11 ++-- .../rule/engine/action/TbMsgCountNode.java | 2 +- .../rule/engine/aws/sns/TbSnsNode.java | 4 +- .../rule/engine/aws/sqs/TbSqsNode.java | 4 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 4 +- .../deduplication/TbMsgDeduplicationNode.java | 6 +-- .../rule/engine/delay/TbMsgDelayNode.java | 2 +- .../engine/edge/AbstractTbMsgPushNode.java | 51 +++++++----------- .../engine/filter/TbAssetTypeSwitchNode.java | 2 +- .../engine/filter/TbCheckRelationNode.java | 4 +- .../engine/filter/TbDeviceTypeSwitchNode.java | 2 +- .../rule/engine/gcp/pubsub/TbPubSubNode.java | 4 +- .../rule/engine/kafka/TbKafkaNode.java | 4 +- .../rule/engine/mail/TbSendEmailNode.java | 7 +-- .../rule/engine/math/TbMathNode.java | 2 +- .../engine/metadata/CalculateDeltaNode.java | 4 +- .../metadata/TbAbstractNodeWithFetchTo.java | 2 +- .../engine/metadata/TbGetTelemetryNode.java | 2 +- .../rule/engine/mqtt/TbMqttNode.java | 2 +- .../notification/TbNotificationNode.java | 3 +- .../rule/engine/profile/DeviceState.java | 31 +++++++---- .../engine/profile/TbDeviceProfileNode.java | 10 ++-- .../rule/engine/rabbitmq/TbRabbitMqNode.java | 2 +- .../rule/engine/rest/TbHttpClient.java | 4 +- .../rule/engine/rpc/TbSendRPCRequestNode.java | 4 +- .../engine/telemetry/TbMsgAttributesNode.java | 2 +- .../engine/telemetry/TbMsgTimeseriesNode.java | 2 +- 34 files changed, 217 insertions(+), 110 deletions(-) create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 76631aaa95..9154c32701 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -223,9 +223,9 @@ public class DefaultTbClusterService implements TbClusterService { if (isRuleChainTransform && isQueueTransform) { tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueName); } else if (isRuleChainTransform) { - tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId); + tbMsg = TbMsg.transformMsgRuleChainId(tbMsg, targetRuleChainId); } else if (isQueueTransform) { - tbMsg = TbMsg.transformMsg(tbMsg, targetQueueName); + tbMsg = TbMsg.transformMsgQueueName(tbMsg, targetQueueName); } return tbMsg; } diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 05d71c4fca..8b09565ef9 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -36,7 +36,6 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.ApiUsageRecordKey; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.EntityType; @@ -103,6 +102,9 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.DataConstants.SCOPE; +import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; + /** * Created by ashvayka on 01.05.18. */ @@ -575,7 +577,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService> tsData = tsService.findLatest(TenantId.SYS_TENANT_ID, device.getId(), PERSISTENT_ATTRIBUTES); future = Futures.transform(tsData, extractDeviceStateData(device), deviceStateExecutor); } else { - ListenableFuture> attrData = attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), DataConstants.SERVER_SCOPE, PERSISTENT_ATTRIBUTES); + ListenableFuture> attrData = attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), SERVER_SCOPE, PERSISTENT_ATTRIBUTES); future = Futures.transform(attrData, extractDeviceStateData(device), deviceStateExecutor); } return transformInactivityTimeout(future); @@ -586,7 +588,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService { attributes.flatMap(KvEntry::getLongValue).ifPresent((inactivityTimeout) -> { if (inactivityTimeout > 0) { @@ -779,7 +781,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService(deviceId, key, value)); } else { - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); } } @@ -806,7 +808,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService(deviceId, key, value)); } else { - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index ed4431f445..02871a59b6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -54,9 +54,53 @@ public class DataConstants { return new String[]{CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE}; } + public static final String ALARM = "ALARM"; public static final String IN = "IN"; public static final String OUT = "OUT"; + public static final String INACTIVITY_EVENT = "INACTIVITY_EVENT"; + public static final String CONNECT_EVENT = "CONNECT_EVENT"; + public static final String DISCONNECT_EVENT = "DISCONNECT_EVENT"; + public static final String ACTIVITY_EVENT = "ACTIVITY_EVENT"; + + public static final String ENTITY_CREATED = "ENTITY_CREATED"; + public static final String ENTITY_UPDATED = "ENTITY_UPDATED"; + public static final String ENTITY_DELETED = "ENTITY_DELETED"; + public static final String ENTITY_ASSIGNED = "ENTITY_ASSIGNED"; + public static final String ENTITY_UNASSIGNED = "ENTITY_UNASSIGNED"; + public static final String ATTRIBUTES_UPDATED = "ATTRIBUTES_UPDATED"; + public static final String ATTRIBUTES_DELETED = "ATTRIBUTES_DELETED"; + public static final String TIMESERIES_UPDATED = "TIMESERIES_UPDATED"; + 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_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"; + public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT"; + public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT"; + public static final String PROVISION_SUCCESS = "PROVISION_SUCCESS"; + public static final String PROVISION_FAILURE = "PROVISION_FAILURE"; + public static final String ENTITY_ASSIGNED_TO_EDGE = "ENTITY_ASSIGNED_TO_EDGE"; + public static final String ENTITY_UNASSIGNED_FROM_EDGE = "ENTITY_UNASSIGNED_FROM_EDGE"; + + public static final String RELATION_ADD_OR_UPDATE = "RELATION_ADD_OR_UPDATE"; + public static final String RELATION_DELETED = "RELATION_DELETED"; + public static final String RELATIONS_DELETED = "RELATIONS_DELETED"; + + public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE"; + + public static final String RPC_QUEUED = "RPC_QUEUED"; + public static final String RPC_SENT = "RPC_SENT"; + public static final String RPC_DELIVERED = "RPC_DELIVERED"; + public static final String RPC_SUCCESSFUL = "RPC_SUCCESSFUL"; + public static final String RPC_TIMEOUT = "RPC_TIMEOUT"; + public static final String RPC_EXPIRED = "RPC_EXPIRED"; + public static final String RPC_FAILED = "RPC_FAILED"; + public static final String RPC_DELETED = "RPC_DELETED"; + public static final String DEFAULT_SECRET_KEY = ""; public static final String SECRET_KEY_FIELD_NAME = "secretKey"; public static final String DURATION_MS_FIELD_NAME = "durationMs"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java index 1e818ac8ef..a7671f4327 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java @@ -38,15 +38,15 @@ public class StringUtils { } public static boolean isBlank(String source) { - return isEmpty(source) || source.trim().isEmpty(); + return source == null || source.isEmpty() || source.trim().isEmpty(); } public static boolean isNotEmpty(String source) { - return !isEmpty(source); + return source != null && !source.isEmpty(); } public static boolean isNotBlank(String source) { - return !isBlank(source); + return source != null && !source.isEmpty() && !source.trim().isEmpty(); } public static String notBlankOrDefault(String src, String def) { diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index 125260def5..bec094b804 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -279,7 +279,7 @@ public final class TbMsg implements Serializable { data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } - public static TbMsg transformMsg(TbMsg tbMsg, TbMsgMetaData metadata) { + public static TbMsg transformMsgMetadata(TbMsg tbMsg, TbMsgMetaData metadata) { return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, metadata.copy(), tbMsg.dataType, tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } @@ -289,17 +289,17 @@ public final class TbMsg implements Serializable { data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } - public static TbMsg transformMsg(TbMsg tbMsg, CustomerId customerId) { + public static TbMsg transformMsgCustomerId(TbMsg tbMsg, CustomerId customerId) { return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } - public static TbMsg transformMsg(TbMsg tbMsg, RuleChainId ruleChainId) { + public static TbMsg transformMsgRuleChainId(TbMsg tbMsg, RuleChainId ruleChainId) { return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, ruleChainId, null, tbMsg.ctx.copy(), tbMsg.getCallback()); } - public static TbMsg transformMsg(TbMsg tbMsg, String queueName) { + public static TbMsg transformMsgQueueName(TbMsg tbMsg, String queueName) { return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, tbMsg.getRuleChainId(), null, tbMsg.ctx.copy(), tbMsg.getCallback()); } @@ -467,4 +467,18 @@ public final class TbMsg implements Serializable { } return ts; } + + public boolean checkType(TbMsgType tbMsgType) { + return tbMsgType != null && tbMsgType.name().equals(this.type); + } + + public boolean checkTypeOneOf(TbMsgType... types) { + for (TbMsgType type : types) { + if (checkType(type)) { + return true; + } + } + return false; + } + } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java new file mode 100644 index 0000000000..ca7c94e9ce --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java @@ -0,0 +1,54 @@ +/** + * 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.session; + +/** + * @deprecated This enum is deprecated and will be removed in a future version. + * Note: This enum was originally part of the public API but is now specific to CoAP transport only. + * Please use {@link org.thingsboard.server.transport.coap.CoapSessionMsgType} instead. + */ +@Deprecated(since="3.5.2", forRemoval = true) +public enum SessionMsgType { + GET_ATTRIBUTES_REQUEST(true), POST_ATTRIBUTES_REQUEST(true), GET_ATTRIBUTES_RESPONSE, + SUBSCRIBE_ATTRIBUTES_REQUEST, UNSUBSCRIBE_ATTRIBUTES_REQUEST, ATTRIBUTES_UPDATE_NOTIFICATION, + + POST_TELEMETRY_REQUEST(true), STATUS_CODE_RESPONSE, + + SUBSCRIBE_RPC_COMMANDS_REQUEST, UNSUBSCRIBE_RPC_COMMANDS_REQUEST, + TO_DEVICE_RPC_REQUEST, TO_DEVICE_RPC_RESPONSE, TO_DEVICE_RPC_RESPONSE_ACK, + + TO_SERVER_RPC_REQUEST(true), TO_SERVER_RPC_RESPONSE, + + RULE_ENGINE_ERROR, + + SESSION_OPEN, SESSION_CLOSE, + + CLAIM_REQUEST(); + + private final boolean requiresRulesProcessing; + + SessionMsgType() { + this(false); + } + + SessionMsgType(boolean requiresRulesProcessing) { + this.requiresRulesProcessing = requiresRulesProcessing; + } + + public boolean requiresRulesProcessing() { + return requiresRulesProcessing; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index 18317481bd..57871c91ff 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -24,10 +24,10 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.server.common.data.msg.TbMsgType; -import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -79,7 +79,7 @@ public abstract class TbAbstractAlarmNode> entityViewsFuture = @@ -94,7 +91,7 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { long startTime = entityView.getStartTimeMs(); long endTime = entityView.getEndTimeMs(); if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) { - if (ATTRIBUTES_DELETED.name().equals(msg.getType())) { + if (msg.checkType(ATTRIBUTES_DELETED)) { List attributes = new ArrayList<>(); for (JsonElement element : JsonParser.parseString(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray()) { if (element.isJsonPrimitive()) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java index 0fd99651a1..e43caf6bb8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java @@ -65,7 +65,7 @@ public class TbMsgCountNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.getType().equals(TbMsgType.MSG_COUNT_SELF_MSG.name()) && msg.getId().equals(nextTickId)) { + if (msg.checkType(TbMsgType.MSG_COUNT_SELF_MSG) && msg.getId().equals(nextTickId)) { JsonObject telemetryJson = new JsonObject(); telemetryJson.addProperty(this.telemetryPrefix + "_" + ctx.getServiceId(), messagesProcessed.longValue()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java index 00d84d3b5f..e24c73f9d3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java @@ -105,13 +105,13 @@ public class TbSnsNode extends TbAbstractExternalNode { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(MESSAGE_ID, result.getMessageId()); metaData.putValue(REQUEST_ID, result.getSdkResponseMetadata().getRequestId()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java index d99827f466..34072fbb8a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java @@ -134,13 +134,13 @@ public class TbSqsNode extends TbAbstractExternalNode { if (!StringUtils.isEmpty(result.getSequenceNumber())) { metaData.putValue(SEQUENCE_NUMBER, result.getSequenceNumber()); } - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private TbMsg processException(TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index febb2c1067..cd32a44ea0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -107,7 +107,7 @@ public class TbMsgGeneratorNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { log.trace("onMsg, config {}, msg {}", config, msg); - if (initialized.get() && msg.getType().equals(TbMsgType.GENERATOR_NODE_SELF_MSG.name()) && msg.getId().equals(nextTickId)) { + if (initialized.get() && msg.checkType(TbMsgType.GENERATOR_NODE_SELF_MSG) && msg.getId().equals(nextTickId)) { TbStopWatch sw = TbStopWatch.create(); withCallback(generate(ctx, msg), m -> { @@ -146,7 +146,7 @@ public class TbMsgGeneratorNode implements TbNode { private ListenableFuture generate(TbContext ctx, TbMsg msg) { log.trace("generate, config {}", config); if (prevMsg == null) { - prevMsg = ctx.newMsg(config.getQueueName(), "", originatorId, msg.getCustomerId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); + prevMsg = ctx.newMsg(config.getQueueName(), TbMsg.EMPTY_STRING, originatorId, msg.getCustomerId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); } if (initialized.get()) { ctx.logJsEvalRequest(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index fae40ff5f5..1c0803770b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -24,10 +24,10 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.msg.TbMsgType; -import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; @@ -80,7 +80,7 @@ public class TbMsgDeduplicationNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - if (TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG.name().equals(msg.getType())) { + if (msg.checkType(TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG)) { processDeduplication(ctx, msg.getOriginator()); } else { processOnRegularMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index dabba5970a..d17415c1a6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -61,7 +61,7 @@ public class TbMsgDelayNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.getType().equals(TbMsgType.DELAY_TIMEOUT_SELF_MSG.name())) { + if (msg.checkType(TbMsgType.DELAY_TIMEOUT_SELF_MSG)) { TbMsg pendingMsg = pendingMsgs.remove(UUID.fromString(msg.getData())); if (pendingMsg != null) { ctx.enqueueForTellNext( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java index bbec000077..3472ea011b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java @@ -67,7 +67,7 @@ public abstract class AbstractTbMsgPushNode metadata = msg.getMetaData().getData(); - EdgeEventActionType actionType = getEdgeEventActionTypeByMsgType(msgType, metadata); + EdgeEventActionType actionType = getEdgeEventActionTypeByMsgType(msg); Map entityBody = new HashMap<>(); JsonNode dataJson = JacksonUtil.toJsonNode(msg.getData()); switch (actionType) { @@ -158,45 +157,31 @@ public abstract class AbstractTbMsgPushNode metadata) { + protected EdgeEventActionType getEdgeEventActionTypeByMsgType(TbMsg msg) { EdgeEventActionType actionType; - if (POST_TELEMETRY_REQUEST.name().equals(msgType) - || TIMESERIES_UPDATED.name().equals(msgType)) { + if (msg.checkTypeOneOf(POST_TELEMETRY_REQUEST, TIMESERIES_UPDATED)) { actionType = EdgeEventActionType.TIMESERIES_UPDATED; - } else if (ATTRIBUTES_UPDATED.name().equals(msgType)) { + } else if (msg.checkType(ATTRIBUTES_UPDATED)) { actionType = EdgeEventActionType.ATTRIBUTES_UPDATED; - } else if (POST_ATTRIBUTES_REQUEST.name().equals(msgType)) { + } else if (msg.checkType(POST_ATTRIBUTES_REQUEST)) { actionType = EdgeEventActionType.POST_ATTRIBUTES; - } else if (ATTRIBUTES_DELETED.name().equals(msgType)) { + } else if (msg.checkType(ATTRIBUTES_DELETED)) { actionType = EdgeEventActionType.ATTRIBUTES_DELETED; - } else if (CONNECT_EVENT.name().equals(msgType) - || DISCONNECT_EVENT.name().equals(msgType) - || ACTIVITY_EVENT.name().equals(msgType) - || INACTIVITY_EVENT.name().equals(msgType)) { - String scope = metadata.get(SCOPE); - if ( StringUtils.isEmpty(scope)) { - actionType = EdgeEventActionType.TIMESERIES_UPDATED; - } else { - actionType = EdgeEventActionType.ATTRIBUTES_UPDATED; - } + } else if (msg.checkTypeOneOf(CONNECT_EVENT, DISCONNECT_EVENT, ACTIVITY_EVENT, INACTIVITY_EVENT)) { + String scope = msg.getMetaData().getValue(SCOPE); + actionType = StringUtils.isEmpty(scope) ? + EdgeEventActionType.TIMESERIES_UPDATED : EdgeEventActionType.ATTRIBUTES_UPDATED; } else { - log.warn("Unsupported msg type [{}]", msgType); - throw new IllegalArgumentException("Unsupported msg type: " + msgType); + String type = msg.getType(); + log.warn("Unsupported msg type [{}]", type); + throw new IllegalArgumentException("Unsupported msg type: " + type); } return actionType; } - protected boolean isSupportedMsgType(String msgType) { - return POST_TELEMETRY_REQUEST.name().equals(msgType) - || POST_ATTRIBUTES_REQUEST.name().equals(msgType) - || ATTRIBUTES_UPDATED.name().equals(msgType) - || ATTRIBUTES_DELETED.name().equals(msgType) - || TIMESERIES_UPDATED.name().equals(msgType) - || ALARM.name().equals(msgType) - || CONNECT_EVENT.name().equals(msgType) - || DISCONNECT_EVENT.name().equals(msgType) - || ACTIVITY_EVENT.name().equals(msgType) - || INACTIVITY_EVENT.name().equals(msgType); + protected boolean isSupportedMsgType(TbMsg msg) { + return msg.checkTypeOneOf(POST_TELEMETRY_REQUEST, POST_ATTRIBUTES_REQUEST, ATTRIBUTES_UPDATED, + ATTRIBUTES_DELETED, TIMESERIES_UPDATED, ALARM, CONNECT_EVENT, DISCONNECT_EVENT, ACTIVITY_EVENT, INACTIVITY_EVENT); } protected boolean isSupportedOriginator(EntityType entityType) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java index d70dd75040..39c37f5c81 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbAssetTypeSwitchNode.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; configClazz = EmptyNodeConfiguration.class, nodeDescription = "Route incoming messages based on the name of the asset profile", nodeDetails = "Route incoming messages based on the name of the asset profile. The asset profile name is case-sensitive.

" + - "Output connections: Message originator profile name or Failure", + "Output connections: Asset profile name or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbAssetTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index 632c1af04f..3af5c9bfe1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -118,11 +118,11 @@ public class TbCheckRelationNode implements TbVersionedNode { throw new TbNodeException("property to update: '" + DIRECTION_PROPERTY_NAME + "' doesn't exists in configuration!"); } String direction = newConfigObjectNode.get(DIRECTION_PROPERTY_NAME).asText(); - if ("TO".equals(direction)) { + if (EntitySearchDirection.TO.name().equals(direction)) { newConfigObjectNode.put(DIRECTION_PROPERTY_NAME, EntitySearchDirection.FROM.name()); return new TbPair<>(true, newConfigObjectNode); } - if ("FROM".equals(direction)) { + if (EntitySearchDirection.FROM.name().equals(direction)) { newConfigObjectNode.put(DIRECTION_PROPERTY_NAME, EntitySearchDirection.TO.name()); return new TbPair<>(true, newConfigObjectNode); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java index 7765a4089d..b146a9be44 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbDeviceTypeSwitchNode.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; configClazz = EmptyNodeConfiguration.class, nodeDescription = "Route incoming messages based on the name of the device profile", nodeDetails = "Route incoming messages based on the name of the device profile. The device profile name is case-sensitive

" + - "Output connections: Message originator profile name or Failure", + "Output connections: Device profile name or Failure", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbNodeEmptyConfig") public class TbDeviceTypeSwitchNode extends TbAbstractTypeSwitchNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java index e7dacbab4e..c55113783c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java @@ -119,13 +119,13 @@ public class TbPubSubNode extends TbAbstractExternalNode { private TbMsg processPublishResult(TbMsg origMsg, String messageId) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(MESSAGE_ID, messageId); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private TbMsg processException(TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private Publisher initPubSubClient() throws IOException { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index 456aac4de1..68094196d0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -176,13 +176,13 @@ public class TbKafkaNode extends TbAbstractExternalNode { metaData.putValue(OFFSET, String.valueOf(recordMetadata.offset())); metaData.putValue(PARTITION, String.valueOf(recordMetadata.partition())); metaData.putValue(TOPIC, recordMetadata.topic()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private TbMsg processException(TbMsg origMsg, Exception e) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java index 78b7b23d5b..6319145637 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java @@ -70,7 +70,7 @@ public class TbSendEmailNode extends TbAbstractExternalNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { try { - validateType(msg.getType()); + validateType(msg); TbEmail email = getEmail(msg); var tbMsg = ackIfNeeded(ctx, msg); withCallback(ctx.getMailExecutor().executeAsync(() -> { @@ -100,8 +100,9 @@ public class TbSendEmailNode extends TbAbstractExternalNode { return email; } - private void validateType(String type) { - if (!TbMsgType.SEND_EMAIL.name().equals(type)) { + private void validateType(TbMsg msg) { + if (!msg.checkType(TbMsgType.SEND_EMAIL)) { + String type = msg.getType(); log.warn("Not expected msg type [{}] for SendEmail Node", type); throw new IllegalStateException("Not expected msg type " + type + " for SendEmail Node"); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java index 33692decc2..fe6deebbcf 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java @@ -248,7 +248,7 @@ public class TbMathNode implements TbNode { } else { md.putValue(mathResultKey, Double.toString(toDoubleValue(mathResultDef, result))); } - return TbMsg.transformMsg(msg, md); + return TbMsg.transformMsgMetadata(msg, md); } private double calculateResult(List args) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java index 3e4e6eb93f..13fe3fae69 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -25,12 +25,12 @@ import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -75,7 +75,7 @@ public class CalculateDeltaNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (!msg.getType().equals(TbMsgType.POST_TELEMETRY_REQUEST.name())) { + if (!msg.checkType(TbMsgType.POST_TELEMETRY_REQUEST)) { ctx.tellNext(msg, TbNodeConnectionType.OTHER); return; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java index 2370bfde29..89e48b5f11 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java @@ -83,7 +83,7 @@ public abstract class TbAbstractNodeWithFetchTo> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(interval, keys)); DonAsynchron.withCallback(list, data -> { var metaData = updateMetadata(data, msg, keys); - ctx.tellSuccess(TbMsg.transformMsg(msg, metaData)); + ctx.tellSuccess(TbMsg.transformMsgMetadata(msg, metaData)); }, error -> ctx.tellFailure(msg, error), ctx.getDbCallbackExecutor()); } catch (Exception e) { ctx.tellFailure(msg, e); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java index d166f86008..23d2132a1b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java @@ -95,7 +95,7 @@ public class TbMqttNode extends TbAbstractExternalNode { private TbMsg processException(TbMsg origMsg, Throwable e) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java index f1fd9727fa..5f1bea2bdb 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java @@ -19,7 +19,6 @@ import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; @@ -77,7 +76,7 @@ public class TbNotificationNode extends TbAbstractExternalNode { ctx.getNotificationCenter().processNotificationRequest(ctx.getTenantId(), notificationRequest, stats -> { TbMsgMetaData metaData = tbMsg.getMetaData().copy(); metaData.putValue("notificationRequestResult", JacksonUtil.toString(stats)); - tellSuccess(ctx, TbMsg.transformMsg(tbMsg, metaData)); + tellSuccess(ctx, TbMsg.transformMsgMetadata(tbMsg, metaData)); })), r -> { }, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index 8cccc51258..7e0b0bc77c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -36,7 +36,6 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.rule.RuleNodeState; @@ -55,6 +54,18 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.msg.TbMsgType.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_ACK; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_CLEAR; +import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_DELETED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ATTRIBUTES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED; +import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UNASSIGNED; +import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; + @Slf4j class DeviceState { @@ -136,24 +147,24 @@ class DeviceState { latestValues = fetchLatestValues(ctx, deviceId); } boolean stateChanged = false; - if (msg.getType().equals(TbMsgType.POST_TELEMETRY_REQUEST.name())) { + if (msg.checkType(POST_TELEMETRY_REQUEST)) { stateChanged = processTelemetry(ctx, msg); - } else if (msg.getType().equals(TbMsgType.POST_ATTRIBUTES_REQUEST.name())) { + } else if (msg.checkType(POST_ATTRIBUTES_REQUEST)) { stateChanged = processAttributesUpdateRequest(ctx, msg); - } else if (msg.getType().equals(TbMsgType.ACTIVITY_EVENT.name()) || msg.getType().equals(TbMsgType.INACTIVITY_EVENT.name())) { + } else if (msg.checkTypeOneOf(ACTIVITY_EVENT, INACTIVITY_EVENT)) { stateChanged = processDeviceActivityEvent(ctx, msg); - } else if (msg.getType().equals(TbMsgType.ATTRIBUTES_UPDATED.name())) { + } else if (msg.checkType(ATTRIBUTES_UPDATED)) { stateChanged = processAttributesUpdateNotification(ctx, msg); - } else if (msg.getType().equals(TbMsgType.ATTRIBUTES_DELETED.name())) { + } else if (msg.checkType(ATTRIBUTES_DELETED)) { stateChanged = processAttributesDeleteNotification(ctx, msg); - } else if (msg.getType().equals(TbMsgType.ALARM_CLEAR.name())) { + } else if (msg.checkType(ALARM_CLEAR)) { stateChanged = processAlarmClearNotification(ctx, msg); - } else if (msg.getType().equals(TbMsgType.ALARM_ACK.name())) { + } else if (msg.checkType(ALARM_ACK)) { processAlarmAckNotification(ctx, msg); - } else if (msg.getType().equals(TbMsgType.ALARM_DELETE.name())) { + } else if (msg.checkType(ALARM_DELETE)) { processAlarmDeleteNotification(ctx, msg); } else { - if (msg.getType().equals(TbMsgType.ENTITY_ASSIGNED.name()) || msg.getType().equals(TbMsgType.ENTITY_UNASSIGNED.name())) { + if (msg.checkTypeOneOf(ENTITY_ASSIGNED, ENTITY_UNASSIGNED)) { dynamicPredicateValueCtx.resetCustomer(); } ctx.tellSuccess(msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index 2de73b6f38..058a70fdea 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -108,16 +108,16 @@ public class TbDeviceProfileNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { EntityType originatorType = msg.getOriginator().getEntityType(); - if (msg.getType().equals(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG.name())) { + if (msg.checkType(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG)) { scheduleAlarmHarvesting(ctx, msg); harvestAlarms(ctx, System.currentTimeMillis()); return; } - if (msg.getType().equals(TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG.name())) { + if (msg.checkType(TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG)) { updateProfile(ctx, new DeviceProfileId(UUID.fromString(msg.getData()))); return; } - if (msg.getType().equals(TbMsgType.DEVICE_UPDATE_SELF_MSG.name())) { + if (msg.checkType(TbMsgType.DEVICE_UPDATE_SELF_MSG)) { JsonNode data = JacksonUtil.toJsonNode(msg.getData()); DeviceId deviceId = new DeviceId(UUID.fromString(data.get("deviceId").asText())); if (data.has("profileId")) { @@ -129,12 +129,12 @@ public class TbDeviceProfileNode implements TbNode { } if (EntityType.DEVICE.equals(originatorType)) { DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); - if (msg.getType().equals(TbMsgType.ENTITY_UPDATED.name())) { + if (msg.checkType(TbMsgType.ENTITY_UPDATED)) { invalidateDeviceProfileCache(deviceId, msg.getData()); ctx.tellSuccess(msg); return; } - if (msg.getType().equals(TbMsgType.ENTITY_DELETED.name())) { + if (msg.checkType(TbMsgType.ENTITY_DELETED)) { removeDeviceState(deviceId); ctx.tellSuccess(msg); return; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java index e83fefa513..be7a22f713 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java @@ -118,7 +118,7 @@ public class TbRabbitMqNode extends TbAbstractExternalNode { private TbMsg processException(TbMsg origMsg, Throwable t) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 70a22a692e..0c9eca5f0a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -286,7 +286,7 @@ public class TbHttpClient { metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); metaData.putValue(ERROR_BODY, response.getBody()); headersToMetaData(response.getHeaders(), metaData::putValue); - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private TbMsg processException(TbMsg origMsg, Throwable e) { @@ -298,7 +298,7 @@ public class TbHttpClient { metaData.putValue(STATUS_CODE, restClientResponseException.getRawStatusCode() + ""); metaData.putValue(ERROR_BODY, restClientResponseException.getResponseBodyAsString()); } - return TbMsg.transformMsg(origMsg, metaData); + return TbMsg.transformMsgMetadata(origMsg, metaData); } private HttpHeaders prepareHeaders(TbMsg msg) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java index 26d22b5ef2..5859cb6f48 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java @@ -27,13 +27,13 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -76,7 +76,7 @@ public class TbSendRPCRequestNode implements TbNode { ctx.tellFailure(msg, new RuntimeException("Params are not present in the message!")); } else { int requestId = json.has("requestId") ? json.get("requestId").getAsInt() : random.nextInt(); - boolean restApiCall = msg.getType().equals(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE.name()); + boolean restApiCall = msg.checkType(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE); tmp = msg.getMetaData().getValue("oneway"); boolean oneway = !StringUtils.isEmpty(tmp) && Boolean.parseBoolean(tmp); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 5ddb1c701f..c7aa99c115 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -65,7 +65,7 @@ public class TbMsgAttributesNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (!msg.getType().equals(POST_ATTRIBUTES_REQUEST.name())) { + if (!msg.checkType(POST_ATTRIBUTES_REQUEST)) { ctx.tellFailure(msg, new IllegalArgumentException("Unsupported msg type: " + msg.getType())); return; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java index 4118d28c22..852b74f64f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java @@ -82,7 +82,7 @@ public class TbMsgTimeseriesNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (!msg.getType().equals(POST_TELEMETRY_REQUEST.name())) { + if (!msg.checkType(POST_TELEMETRY_REQUEST)) { ctx.tellFailure(msg, new IllegalArgumentException("Unsupported msg type: " + msg.getType())); return; } From b95eae215a34fd5a900c5120416ebc3210938745 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 27 Jul 2023 15:26:40 +0300 Subject: [PATCH 085/166] replaced handling of AssetProfile and DeviceProfile with HasRuleEngineProfile to match PE code version --- .../queue/DefaultTbClusterService.java | 66 ++++++++----------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 9154c32701..63eaab23f2 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -32,10 +32,10 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasRuleEngineProfile; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.AssetId; @@ -178,15 +178,8 @@ public class DefaultTbClusterService implements TbClusterService { return; } } else { - if (entityId.getEntityType().equals(EntityType.DEVICE)) { - tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceId(entityId.getId()))); - } else if (entityId.getEntityType().equals(EntityType.DEVICE_PROFILE)) { - tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId()))); - } else if (entityId.getEntityType().equals(EntityType.ASSET)) { - tbMsg = transformMsg(tbMsg, assetProfileCache.get(tenantId, new AssetId(entityId.getId()))); - } else if (entityId.getEntityType().equals(EntityType.ASSET_PROFILE)) { - tbMsg = transformMsg(tbMsg, assetProfileCache.get(tenantId, new AssetProfileId(entityId.getId()))); - } + HasRuleEngineProfile ruleEngineProfile = getRuleEngineProfileForEntityOrElseNull(tenantId, entityId); + tbMsg = transformMsg(tbMsg, ruleEngineProfile); } TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueName(), tenantId, entityId); log.trace("PUSHING msg: {} to:{}", tbMsg, tpi); @@ -198,34 +191,33 @@ public class DefaultTbClusterService implements TbClusterService { toRuleEngineMsgs.incrementAndGet(); } - private TbMsg transformMsg(TbMsg tbMsg, DeviceProfile deviceProfile) { - if (deviceProfile != null) { - RuleChainId targetRuleChainId = deviceProfile.getDefaultRuleChainId(); - String targetQueueName = deviceProfile.getDefaultQueueName(); - tbMsg = transformMsg(tbMsg, targetRuleChainId, targetQueueName); - } - return tbMsg; - } - - private TbMsg transformMsg(TbMsg tbMsg, AssetProfile assetProfile) { - if (assetProfile != null) { - RuleChainId targetRuleChainId = assetProfile.getDefaultRuleChainId(); - String targetQueueName = assetProfile.getDefaultQueueName(); - tbMsg = transformMsg(tbMsg, targetRuleChainId, targetQueueName); + private HasRuleEngineProfile getRuleEngineProfileForEntityOrElseNull(TenantId tenantId, EntityId entityId) { + if (entityId.getEntityType().equals(EntityType.DEVICE)) { + return deviceProfileCache.get(tenantId, new DeviceId(entityId.getId())); + } else if (entityId.getEntityType().equals(EntityType.DEVICE_PROFILE)) { + return deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId())); + } else if (entityId.getEntityType().equals(EntityType.ASSET)) { + return assetProfileCache.get(tenantId, new AssetId(entityId.getId())); + } else if (entityId.getEntityType().equals(EntityType.ASSET_PROFILE)) { + return assetProfileCache.get(tenantId, new AssetProfileId(entityId.getId())); } - return tbMsg; - } - - private TbMsg transformMsg(TbMsg tbMsg, RuleChainId targetRuleChainId, String targetQueueName) { - boolean isRuleChainTransform = targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId()); - boolean isQueueTransform = targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName()); - - if (isRuleChainTransform && isQueueTransform) { - tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueName); - } else if (isRuleChainTransform) { - tbMsg = TbMsg.transformMsgRuleChainId(tbMsg, targetRuleChainId); - } else if (isQueueTransform) { - tbMsg = TbMsg.transformMsgQueueName(tbMsg, targetQueueName); + return null; + } + private TbMsg transformMsg(TbMsg tbMsg, HasRuleEngineProfile ruleEngineProfile) { + if (ruleEngineProfile != null) { + RuleChainId targetRuleChainId = ruleEngineProfile.getDefaultRuleChainId(); + String targetQueueName = ruleEngineProfile.getDefaultQueueName(); + + boolean isRuleChainTransform = targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId()); + boolean isQueueTransform = targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName()); + + if (isRuleChainTransform && isQueueTransform) { + tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueName); + } else if (isRuleChainTransform) { + tbMsg = TbMsg.transformMsgRuleChainId(tbMsg, targetRuleChainId); + } else if (isQueueTransform) { + tbMsg = TbMsg.transformMsgQueueName(tbMsg, targetQueueName); + } } return tbMsg; } From b69f63660b5e8a9e1b8c1a0e90d35d9a9253fe41 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 27 Jul 2023 16:59:26 +0300 Subject: [PATCH 086/166] UI: Device connectivity change coap install instruction and added support spartplug --- ...e-check-connectivity-dialog.component.html | 168 ++++++++++-------- ...e-check-connectivity-dialog.component.scss | 5 +- ...ice-check-connectivity-dialog.component.ts | 42 +---- ui-ngx/src/app/shared/models/device.models.ts | 1 + .../assets/locale/locale.constant-en_US.json | 2 + 5 files changed, 100 insertions(+), 118 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html index 0f9c6dc055..71a8364134 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html @@ -73,7 +73,7 @@
device.connectivity.install-necessary-client-tools
-
device.connectivity.install-curl-windows
+
device.connectivity.install-curl-windows
-
device.connectivity.use-following-instructions
- - - - - Windows - - -
-
-
device.connectivity.install-necessary-client-tools
-
- + + +
+ +
device.connectivity.use-following-instructions
+ + + + + Windows + + +
+
+
device.connectivity.install-necessary-client-tools
+
+ - + +
-
- - -
- - - - - - MacOS - - -
-
-
device.connectivity.install-necessary-client-tools
- +
- + MacOS + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ - -
-
- - - - - Linux - - -
-
-
device.connectivity.install-necessary-client-tools
- +
- + Linux + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ - -
-
- - - - - Docker - - -
- + + + Docker + + +
+ - -
-
- - +
+
+
+
+ +
device.connectivity.use-following-instructions
@@ -226,10 +234,14 @@
-
+
device.connectivity.install-necessary-client-tools
- +
+ + +
-
+
device.connectivity.install-necessary-client-tools
- +
+ + +
Date: Thu, 27 Jul 2023 17:18:39 +0300 Subject: [PATCH 087/166] UI: Implement Value card widget settings. Improve widget container layout. --- .../json/system/widget_bundles/cards.json | 16 +- .../core/services/dashboard-utils.service.ts | 6 +- .../add-widget-dialog.component.scss | 4 +- .../dashboard-page.component.ts | 1 + .../dashboard-widget-select.component.scss | 2 + .../value-card-basic-config.component.html | 46 ++-- .../value-card-basic-config.component.ts | 10 + .../basic/common/data-key-row.component.html | 6 +- .../basic/common/data-key-row.component.scss | 13 +- .../common/data-keys-panel.component.html | 4 +- .../common/data-keys-panel.component.scss | 12 +- .../widget/config/data-keys.component.html | 2 +- .../widget/config/data-keys.component.scss | 8 +- .../config/widget-settings.component.ts | 10 + .../widget/config/widget-settings.models.ts | 20 +- .../value-card-widget-settings.component.html | 89 ++++++++ .../value-card-widget-settings.component.ts | 200 ++++++++++++++++++ .../background-settings-panel.component.html | 87 ++++++++ .../background-settings-panel.component.scss | 73 +++++++ .../background-settings-panel.component.ts | 120 +++++++++++ .../common/background-settings.component.html | 30 +++ .../common/background-settings.component.scss | 41 ++++ .../common/background-settings.component.ts | 120 +++++++++++ .../common/image-cards-select.component.ts | 27 ++- .../lib/settings/widget-settings.module.ts | 20 +- .../widget/widget-component.service.ts | 3 + .../widget/widget-config.component.html | 1 + .../widget/widget-container.component.html | 17 +- .../widget/widget-container.component.scss | 47 ++-- .../components/widget/widget.component.ts | 1 + .../home/models/widget-component.models.ts | 2 + .../components/unit-input.component.html | 2 +- .../shared/components/unit-input.component.ts | 19 +- ui-ngx/src/app/shared/models/unit.models.ts | 6 + ui-ngx/src/app/shared/models/widget.models.ts | 18 +- .../assets/locale/locale.constant-en_US.json | 16 +- .../src/assets/{model => metadata}/units.json | 0 ui-ngx/src/styles.scss | 25 +-- 38 files changed, 1020 insertions(+), 104 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts rename ui-ngx/src/assets/{model => metadata}/units.json (100%) diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index cc2c74c359..1289923667 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -229,19 +229,19 @@ { "alias": "value_card", "name": "Value card", - "image": null, + "image": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyNyIgZmlsbD0ibm9uZSIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTI4IDEyNyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KIDxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2RfMTE0Ml8yMDM5NTQpIj4KICA8cmVjdCB4PSI1LjUiIHk9IjIuNSIgd2lkdGg9IjExNyIgaGVpZ2h0PSIxMTciIHJ4PSIyLjI5NDEiIGZpbGw9IiNmZmYiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPgogIDxwYXRoIGQ9Im0zMy42MDMgMjkuMjIxdi03LjY0NzFjMC0xLjU4NjgtMS4yODA4LTIuODY3Ni0yLjg2NzYtMi44Njc2cy0yLjg2NzcgMS4yODA4LTIuODY3NyAyLjg2NzZ2Ny42NDcxYy0xLjE1NjYgMC44Njk4LTEuOTExNyAyLjI2NTQtMS45MTE3IDMuODIzNSAwIDIuNjM4MiAyLjE0MTIgNC43Nzk0IDQuNzc5NCA0Ljc3OTRzNC43Nzk0LTIuMTQxMiA0Ljc3OTQtNC43Nzk0YzAtMS41NTgxLTAuNzU1MS0yLjk1MzctMS45MTE4LTMuODIzNXptLTMuODIzNS03LjY0NzFjMC0wLjUyNTcgMC40MzAyLTAuOTU1OSAwLjk1NTktMC45NTU5czAuOTU1OSAwLjQzMDIgMC45NTU5IDAuOTU1OWgtMC45NTU5djAuOTU1OWgwLjk1NTl2MS45MTE3aC0wLjk1NTl2MC45NTU5aDAuOTU1OXYxLjkxMThoLTEuOTExOHYtNS43MzUzeiIgZmlsbD0iIzU0NjlGRiIvPgogIDxnIGZpbGw9IiMwMDAiPgogICA8cGF0aCBkPSJtNTAuMTQxIDE5Ljc0MXY2LjUyMzhoLTEuMTE1N3YtNi41MjM4aDEuMTE1N3ptMi4wNDc3IDB2MC44OTYxaC01LjE5MzJ2LTAuODk2MWg1LjE5MzJ6bTIuNjAzMyA2LjYxMzVjLTAuMzU4NSAwLTAuNjgyNi0wLjA1ODMtMC45NzIzLTAuMTc0OC0wLjI4NjgtMC4xMTk1LTAuNTMxOC0wLjI4NTMtMC43MzQ5LTAuNDk3My0wLjIwMDEtMC4yMTIxLTAuMzU0LTAuNDYxNi0wLjQ2MTUtMC43NDgzLTAuMTA3NS0wLjI4NjgtMC4xNjEzLTAuNTk2LTAuMTYxMy0wLjkyNzV2LTAuMTc5M2MwLTAuMzc5MyAwLjA1NTMtMC43MjI4IDAuMTY1OC0xLjAzMDVzMC4yNjQzLTAuNTcwNiAwLjQ2MTUtMC43ODg2YzAuMTk3MS0wLjIyMTEgMC40MzAxLTAuMzg5OCAwLjY5OS0wLjUwNjMgMC4yNjg4LTAuMTE2NSAwLjU2MDEtMC4xNzQ4IDAuODczNy0wLjE3NDggMC4zNDY1IDAgMC42NDk3IDAuMDU4MyAwLjkwOTYgMC4xNzQ4czAuNDc1IDAuMjgwOCAwLjY0NTIgMC40OTI4YzAuMTczMyAwLjIwOTEgMC4zMDE3IDAuNDU4NiAwLjM4NTQgMC43NDgzIDAuMDg2NiAwLjI4OTggMC4xMjk5IDAuNjA5NCAwLjEyOTkgMC45NTg5djAuNDYxNWgtMy43NDU5di0wLjc3NTJoMi42Nzk1di0wLjA4NTFjLTZlLTMgLTAuMTk0Mi0wLjA0NDgtMC4zNzY0LTAuMTE2NS0wLjU0NjYtMC4wNjg3LTAuMTcwMy0wLjE3NDctMC4zMDc3LTAuMzE4MS0wLjQxMjMtMC4xNDM0LTAuMTA0NS0wLjMzNDYtMC4xNTY4LTAuNTczNi0wLjE1NjgtMC4xNzkyIDAtMC4zMzkgMC4wMzg4LTAuNDc5NCAwLjExNjUtMC4xMzc0IDAuMDc0Ny0wLjI1MjQgMC4xODM3LTAuMzQ1IDAuMzI3MXMtMC4xNjQzIDAuMzE2Ni0wLjIxNTEgMC41MTk4Yy0wLjA0NzggMC4yMDAxLTAuMDcxNyAwLjQyNTYtMC4wNzE3IDAuNjc2NXYwLjE3OTNjMCAwLjIxMjEgMC4wMjg0IDAuNDA5MiAwLjA4NTIgMC41OTE0IDAuMDU5NyAwLjE3OTMgMC4xNDYzIDAuMzM2MSAwLjI1OTggMC40NzA1IDAuMTEzNiAwLjEzNDQgMC4yNTEgMC4yNDA1IDAuNDEyMyAwLjMxODEgMC4xNjEzIDAuMDc0NyAwLjM0NSAwLjExMiAwLjU1MTEgMC4xMTIgMC4yNTk5IDAgMC40OTE0LTAuMDUyMiAwLjY5NDUtMC4xNTY4IDAuMjAzMS0wLjEwNDUgMC4zNzk0LTAuMjUyNCAwLjUyODctMC40NDM2bDAuNTY5MSAwLjU1MTJjLTAuMTA0NiAwLjE1MjMtMC4yNDA1IDAuMjk4Ny0wLjQwNzggMC40MzkxLTAuMTY3MyAwLjEzNzQtMC4zNzE5IDAuMjQ5NC0wLjYxMzggMC4zMzYtMC4yMzkgMC4wODY2LTAuNTE2OCAwLjEzLTAuODMzNCAwLjEzem00LjAwNTctMy45NTJ2My44NjIzaC0xLjA3OTh2LTQuODQ4MWgxLjAxNzFsMC4wNjI3IDAuOTg1OHptLTAuMTc0NyAxLjI1OTEtMC4zNjc1LTAuMDA0NWMwLTAuMzM0NiAwLjA0MTktMC42NDM3IDAuMTI1NS0wLjkyNzVzMC4yMDYxLTAuNTMwMiAwLjM2NzQtMC43MzkzYzAuMTYxMy0wLjIxMjEgMC4zNjE1LTAuMzc0OSAwLjYwMDQtMC40ODg0IDAuMjQyLTAuMTE2NSAwLjUyMTMtMC4xNzQ4IDAuODM3OS0wLjE3NDggMC4yMjExIDAgMC40MjI3IDAuMDMyOSAwLjYwNDkgMC4wOTg2IDAuMTg1MiAwLjA2MjcgMC4zNDUgMC4xNjI4IDAuNDc5NSAwLjMwMDIgMC4xMzc0IDAuMTM3NCAwLjI0MTkgMC4zMTM2IDAuMzEzNiAwLjUyODcgMC4wNzQ3IDAuMjE1MSAwLjExMiAwLjQ3NSAwLjExMiAwLjc3OTd2My4yMzA1aC0xLjA3OTh2LTMuMTM2NGMwLTAuMjM2LTAuMDM1OS0wLjQyMTItMC4xMDc2LTAuNTU1Ni0wLjA2ODctMC4xMzQ1LTAuMTY4Ny0wLjIzMDEtMC4zMDAyLTAuMjg2OC0wLjEyODQtMC4wNTk4LTAuMjgyMy0wLjA4OTYtMC40NjE1LTAuMDg5Ni0wLjIwMzEgMC0wLjM3NjQgMC4wMzg4LTAuNTE5NyAwLjExNjUtMC4xNDA0IDAuMDc3Ni0wLjI1NTQgMC4xODM3LTAuMzQ1MSAwLjMxODEtMC4wODk2IDAuMTM0NC0wLjE1NTMgMC4yODk4LTAuMTk3MSAwLjQ2NnMtMC4wNjI3IDAuMzY0NC0wLjA2MjcgMC41NjQ2em0zLjAwNjUtMC4yODY4LTAuNTA2MyAwLjExMmMwLTAuMjkyNyAwLjA0MDMtMC41NjkgMC4xMjEtMC44Mjg5IDAuMDgzNi0wLjI2MjkgMC4yMDQ2LTAuNDkyOSAwLjM2MjktMC42OSAwLjE2MTMtMC4yMDAyIDAuMzYtMC4zNTcgMC41OTU5LTAuNDcwNSAwLjIzNi0wLjExMzUgMC41MDY0LTAuMTcwMyAwLjgxMS0wLjE3MDMgMC4yNDggMCAwLjQ2OSAwLjAzNDQgMC42NjMyIDAuMTAzMSAwLjE5NzEgMC4wNjU3IDAuMzY0NCAwLjE3MDIgMC41MDE4IDAuMzEzNnMwLjI0MiAwLjMzMDEgMC4zMTM3IDAuNTYwMWMwLjA3MTcgMC4yMjcgMC4xMDc1IDAuNTAxOCAwLjEwNzUgMC44MjQ1djMuMTM2NGgtMS4wODQzdi0zLjE0MDljMC0wLjI0NS0wLjAzNTktMC40MzQ2LTAuMTA3Ni0wLjU2OTEtMC4wNjg3LTAuMTM0NC0wLjE2NzItMC4yMjctMC4yOTU3LTAuMjc3OC0wLjEyODQtMC4wNTM3LTAuMjgyMy0wLjA4MDYtMC40NjE1LTAuMDgwNi0wLjE2NzMgMC0wLjMxNTEgMC4wMzEzLTAuNDQzNiAwLjA5NDEtMC4xMjU0IDAuMDU5Ny0wLjIzMTUgMC4xNDQ4LTAuMzE4MSAwLjI1NTQtMC4wODY2IDAuMTA3NS0wLjE1MjQgMC4yMzE1LTAuMTk3MiAwLjM3MTktMC4wNDE4IDAuMTQwNC0wLjA2MjcgMC4yOTI3LTAuMDYyNyAwLjQ1N3ptNS4zMDk2LTEuMDI2MXY1Ljc4MDFoLTEuMDc5OHYtNi43MTIxaDAuOTk0N2wwLjA4NTEgMC45MzJ6bTMuMTU4OSAxLjQ0NzN2MC4wOTQxYzAgMC4zNTI1LTAuMDQxOCAwLjY3OTYtMC4xMjU0IDAuOTgxMy0wLjA4MDcgMC4yOTg3LTAuMjAxNyAwLjU2LTAuMzYzIDAuNzg0MS0wLjE1ODMgMC4yMjEtMC4zNTM5IDAuMzkyOC0wLjU4NjkgMC41MTUzLTAuMjMzIDAuMTIyNC0wLjUwMTkgMC4xODM3LTAuODA2NiAwLjE4MzctMC4zMDE3IDAtMC41NjYtMC4wNTUzLTAuNzkzLTAuMTY1OC0wLjIyNDEtMC4xMTM1LTAuNDEzOC0wLjI3MzMtMC41NjkxLTAuNDc5NS0wLjE1NTMtMC4yMDYxLTAuMjgwOC0wLjQ0OC0wLjM3NjQtMC43MjU4LTAuMDkyNi0wLjI4MDgtMC4xNTgzLTAuNTg4NS0wLjE5NzEtMC45MjMxdi0wLjM2MjljMC4wMzg4LTAuMzU1NSAwLjEwNDUtMC42NzgxIDAuMTk3MS0wLjk2NzggMC4wOTU2LTAuMjg5OCAwLjIyMTEtMC41MzkyIDAuMzc2NC0wLjc0ODNzMC4zNDUtMC4zNzA0IDAuNTY5MS0wLjQ4MzljMC4yMjQtMC4xMTM1IDAuNDg1NC0wLjE3MDMgMC43ODQxLTAuMTcwMyAwLjMwNDcgMCAwLjU3NSAwLjA1OTggMC44MTEgMC4xNzkyIDAuMjM2IDAuMTE2NSAwLjQzNDYgMC4yODM4IDAuNTk1OSAwLjUwMTkgMC4xNjEzIDAuMjE1MSAwLjI4MjMgMC40NzQ5IDAuMzYyOSAwLjc3OTYgMC4wODA3IDAuMzAxNyAwLjEyMSAwLjYzNzggMC4xMjEgMS4wMDgyem0tMS4wNzk4IDAuMDk0MXYtMC4wOTQxYzAtMC4yMjQxLTAuMDIwOS0wLjQzMTctMC4wNjI3LTAuNjIyOC0wLjA0MTktMC4xOTQyLTAuMTA3Ni0wLjM2NDUtMC4xOTcyLTAuNTEwOC0wLjA4OTYtMC4xNDY0LTAuMjA0Ni0wLjI1OTktMC4zNDUtMC4zNDA2LTAuMTM3NC0wLjA4MzYtMC4zMDMyLTAuMTI1NC0wLjQ5NzQtMC4xMjU0LTAuMTkxMSAwLTAuMzU1NCAwLjAzMjgtMC40OTI4IDAuMDk4NS0wLjEzNzUgMC4wNjI4LTAuMjUyNSAwLjE1MDktMC4zNDUxIDAuMjY0NHMtMC4xNjQzIDAuMjQ2NC0wLjIxNSAwLjM5ODhjLTAuMDUwOCAwLjE0OTMtMC4wODY3IDAuMzEyMS0wLjEwNzYgMC40ODg0djAuODY5MmMwLjAzNTkgMC4yMTUxIDAuMDk3MSAwLjQxMjMgMC4xODM3IDAuNTkxNSAwLjA4NjcgMC4xNzkyIDAuMjA5MSAwLjMyMjYgMC4zNjc1IDAuNDMwMSAwLjE2MTMgMC4xMDQ2IDAuMzY3NCAwLjE1NjkgMC42MTgzIDAuMTU2OSAwLjE5NDIgMCAwLjM1OTktMC4wNDE5IDAuNDk3My0wLjEyNTUgMC4xMzc1LTAuMDgzNiAwLjI0OTUtMC4xOTg2IDAuMzM2MS0wLjM0NSAwLjA4OTYtMC4xNDk0IDAuMTU1My0wLjMyMTEgMC4xOTcyLTAuNTE1MyAwLjA0MTgtMC4xOTQxIDAuMDYyNy0wLjQwMDMgMC4wNjI3LTAuNjE4M3ptNC4yNzkgMi40NjQ0Yy0wLjM1ODQgMC0wLjY4MjUtMC4wNTgzLTAuOTcyMy0wLjE3NDgtMC4yODY3LTAuMTE5NS0wLjUzMTctMC4yODUzLTAuNzM0OC0wLjQ5NzMtMC4yMDAxLTAuMjEyMS0wLjM1NC0wLjQ2MTYtMC40NjE1LTAuNzQ4My0wLjEwNzUtMC4yODY4LTAuMTYxMy0wLjU5Ni0wLjE2MTMtMC45Mjc1di0wLjE3OTNjMC0wLjM3OTMgMC4wNTUyLTAuNzIyOCAwLjE2NTgtMS4wMzA1IDAuMTEwNS0wLjMwNzcgMC4yNjQzLTAuNTcwNiAwLjQ2MTUtMC43ODg2IDAuMTk3MS0wLjIyMTEgMC40MzAxLTAuMzg5OCAwLjY5OS0wLjUwNjMgMC4yNjg4LTAuMTE2NSAwLjU2MDEtMC4xNzQ4IDAuODczNy0wLjE3NDggMC4zNDY1IDAgMC42NDk3IDAuMDU4MyAwLjkwOTYgMC4xNzQ4czAuNDc0OSAwLjI4MDggMC42NDUyIDAuNDkyOGMwLjE3MzMgMC4yMDkxIDAuMzAxNyAwLjQ1ODYgMC4zODUzIDAuNzQ4MyAwLjA4NjcgMC4yODk4IDAuMTMgMC42MDk0IDAuMTMgMC45NTg5djAuNDYxNWgtMy43NDU5di0wLjc3NTJoMi42Nzk1di0wLjA4NTFjLTZlLTMgLTAuMTk0Mi0wLjA0NDgtMC4zNzY0LTAuMTE2NS0wLjU0NjYtMC4wNjg3LTAuMTcwMy0wLjE3NDgtMC4zMDc3LTAuMzE4MS0wLjQxMjMtMC4xNDM0LTAuMTA0NS0wLjMzNDYtMC4xNTY4LTAuNTczNi0wLjE1NjgtMC4xNzkyIDAtMC4zMzkgMC4wMzg4LTAuNDc5NCAwLjExNjUtMC4xMzc0IDAuMDc0Ny0wLjI1MjQgMC4xODM3LTAuMzQ1IDAuMzI3MXMtMC4xNjQzIDAuMzE2Ni0wLjIxNTEgMC41MTk4Yy0wLjA0NzggMC4yMDAxLTAuMDcxNyAwLjQyNTYtMC4wNzE3IDAuNjc2NXYwLjE3OTNjMCAwLjIxMjEgMC4wMjg0IDAuNDA5MiAwLjA4NTEgMC41OTE0IDAuMDU5OCAwLjE3OTMgMC4xNDY0IDAuMzM2MSAwLjI1OTkgMC40NzA1czAuMjUwOSAwLjI0MDUgMC40MTIzIDAuMzE4MWMwLjE2MTMgMC4wNzQ3IDAuMzQ1IDAuMTEyIDAuNTUxMSAwLjExMiAwLjI1OTkgMCAwLjQ5MTQtMC4wNTIyIDAuNjk0NS0wLjE1NjggMC4yMDMxLTAuMTA0NSAwLjM3OTQtMC4yNTI0IDAuNTI4Ny0wLjQ0MzZsMC41NjkxIDAuNTUxMmMtMC4xMDQ2IDAuMTUyMy0wLjI0MDUgMC4yOTg3LTAuNDA3OCAwLjQzOTEtMC4xNjczIDAuMTM3NC0wLjM3MTkgMC4yNDk0LTAuNjEzOCAwLjMzNi0wLjIzOSAwLjA4NjYtMC41MTY4IDAuMTMtMC44MzM1IDAuMTN6bTQuMDEwMy00LjAxNDd2My45MjVoLTEuMDc5OXYtNC44NDgxaDEuMDMwNmwwLjA0OTMgMC45MjMxem0xLjQ4MzEtMC45NTQ0LTllLTMgMS4wMDM2Yy0wLjA2NTctMC4wMTE5LTAuMTM3NC0wLjAyMDktMC4yMTUxLTAuMDI2OC0wLjA3NDYtNmUtMyAtMC4xNDkzLTllLTMgLTAuMjI0LTllLTMgLTAuMTg1MiAwLTAuMzQ4IDAuMDI2OS0wLjQ4ODQgMC4wODA3LTAuMTQwNCAwLjA1MDctMC4yNTg0IDAuMTI1NC0wLjM1NCAwLjIyNC0wLjA5MjYgMC4wOTU2LTAuMTY0MyAwLjIxMjEtMC4yMTUgMC4zNDk1LTAuMDUwOCAwLjEzNzQtMC4wODA3IDAuMjkxMi0wLjA4OTYgMC40NjE1bC0wLjI0NjUgMC4wMTc5YzAtMC4zMDQ3IDAuMDI5OS0wLjU4NyAwLjA4OTYtMC44NDY4IDAuMDU5OC0wLjI1OTkgMC4xNDk0LTAuNDg4NCAwLjI2ODktMC42ODU2IDAuMTIyNC0wLjE5NzEgMC4yNzQ4LTAuMzUxIDAuNDU3LTAuNDYxNSAwLjE4NTItMC4xMTA1IDAuMzk4OC0wLjE2NTggMC42NDA3LTAuMTY1OCAwLjA2NTggMCAwLjEzNiA2ZS0zIDAuMjEwNiAwLjAxNzkgMC4wNzc3IDAuMDEyIDAuMTM2IDAuMDI1NCAwLjE3NDggMC4wNDA0em0zLjM5MTkgMy45MDcxdi0yLjMxMmMwLTAuMTczMy0wLjAzMTQtMC4zMjI2LTAuMDk0MS0wLjQ0ODEtMC4wNjI4LTAuMTI1NC0wLjE1ODMtMC4yMjI1LTAuMjg2OC0wLjI5MTItMC4xMjU0LTAuMDY4Ny0wLjI4MzgtMC4xMDMxLTAuNDc0OS0wLjEwMzEtMC4xNzYzIDAtMC4zMjg2IDAuMDI5OS0wLjQ1NzEgMC4wODk2LTAuMTI4NCAwLjA1OTgtMC4yMjg1IDAuMTQwNC0wLjMwMDIgMC4yNDJzLTAuMTA3NSAwLjIxNjYtMC4xMDc1IDAuMzQ1aC0xLjA3NTRjMC0wLjE5MTIgMC4wNDYzLTAuMzc2NCAwLjEzODktMC41NTU2czAuMjI3LTAuMzM5IDAuNDAzMy0wLjQ3OTRjMC4xNzYyLTAuMTQwNCAwLjM4NjgtMC4yNTA5IDAuNjMxOC0wLjMzMTYgMC4yNDQ5LTAuMDgwNyAwLjUxOTctMC4xMjEgMC44MjQ0LTAuMTIxIDAuMzY0NCAwIDAuNjg3IDAuMDYxMyAwLjk2NzggMC4xODM3IDAuMjgzOCAwLjEyMjUgMC41MDY0IDAuMzA3NyAwLjY2NzcgMC41NTU2IDAuMTY0MyAwLjI0NSAwLjI0NjQgMC41NTI3IDAuMjQ2NCAwLjkyMzF2Mi4xNTUyYzAgMC4yMjEgMC4wMTQ5IDAuNDE5NyAwLjA0NDggMC41OTU5IDAuMDMyOSAwLjE3MzMgMC4wNzkyIDAuMzI0MSAwLjEzODkgMC40NTI2djAuMDcxNmgtMS4xMDY3Yy0wLjA1MDgtMC4xMTY0LTAuMDkxMS0wLjI2NDMtMC4xMjEtMC40NDM1LTAuMDI2OS0wLjE4MjMtMC4wNDAzLTAuMzU4NS0wLjA0MDMtMC41Mjg4em0wLjE1NjgtMS45NzYgOWUtMyAwLjY2NzdoLTAuNzc1MmMtMC4yMDAxIDAtMC4zNzY0IDAuMDE5NC0wLjUyODcgMC4wNTgyLTAuMTUyNCAwLjAzNTktMC4yNzkzIDAuMDg5Ni0wLjM4MDkgMC4xNjEzLTAuMTAxNSAwLjA3MTctMC4xNzc3IDAuMTU4My0wLjIyODUgMC4yNTk5cy0wLjA3NjIgMC4yMTY2LTAuMDc2MiAwLjM0NWMwIDAuMTI4NSAwLjAyOTkgMC4yNDY1IDAuMDg5NiAwLjM1NCAwLjA1OTggMC4xMDQ1IDAuMTQ2NCAwLjE4NjcgMC4yNTk5IDAuMjQ2NCAwLjExNjUgMC4wNTk4IDAuMjU2OSAwLjA4OTYgMC40MjEyIDAuMDg5NiAwLjIyMTEgMCAwLjQxMzctMC4wNDQ4IDAuNTc4LTAuMTM0NCAwLjE2NzMtMC4wOTI2IDAuMjk4Ny0wLjIwNDYgMC4zOTQzLTAuMzM2IDAuMDk1Ni0wLjEzNDQgMC4xNDY0LTAuMjYxNCAwLjE1MjQtMC4zODA5bDAuMzQ5NSAwLjQ3OTVjLTAuMDM1OSAwLjEyMjQtMC4wOTcxIDAuMjUzOS0wLjE4MzggMC4zOTQzLTAuMDg2NiAwLjE0MDMtMC4yMDAxIDAuMjc0OC0wLjM0MDUgMC40MDMyLTAuMTM3NCAwLjEyNTUtMC4zMDMyIDAuMjI4NS0wLjQ5NzMgMC4zMDkyLTAuMTkxMiAwLjA4MDYtMC40MTIzIDAuMTIxLTAuNjYzMiAwLjEyMS0wLjMxNjYgMC0wLjU5ODktMC4wNjI4LTAuODQ2OC0wLjE4ODItMC4yNDgtMC4xMjg1LTAuNDQyMS0wLjMwMDItMC41ODI1LTAuNTE1My0wLjE0MDQtMC4yMTgxLTAuMjEwNi0wLjQ2NDUtMC4yMTA2LTAuNzM5MyAwLTAuMjU2OSAwLjA0NzgtMC40ODM5IDAuMTQzNC0wLjY4MTEgMC4wOTg1LTAuMjAwMSAwLjI0MTktMC4zNjc0IDAuNDMwMS0wLjUwMTggMC4xOTEyLTAuMTM0NCAwLjQyNDItMC4yMzYgMC42OTktMC4zMDQ3IDAuMjc0OC0wLjA3MTcgMC41ODg1LTAuMTA3NiAwLjk0MDktMC4xMDc2aDAuODQ2OXptNC40MjI0LTEuODk5OHYwLjc4ODZoLTIuNzMzMnYtMC43ODg2aDIuNzMzMnptLTEuOTQ0Ni0xLjE4NzRoMS4wNzk5djQuNjk1OGMwIDAuMTQ5NCAwLjAyMDkgMC4yNjQ0IDAuMDYyNyAwLjM0NSAwLjA0NDggMC4wNzc3IDAuMTA2IDAuMTMgMC4xODM3IDAuMTU2OSAwLjA3NzcgMC4wMjY4IDAuMTY4OCAwLjA0MDMgMC4yNzMzIDAuMDQwMyAwLjA3NDcgMCAwLjE0NjQtMC4wMDQ1IDAuMjE1MS0wLjAxMzUgMC4wNjg3LTAuMDA4OSAwLjEyNC0wLjAxNzkgMC4xNjU4LTAuMDI2OGwwLjAwNDUgMC44MjQ0Yy0wLjA4OTYgMC4wMjY5LTAuMTk0MiAwLjA1MDgtMC4zMTM3IDAuMDcxNy0wLjExNjUgMC4wMjA5LTAuMjUwOSAwLjAzMTQtMC40MDMyIDAuMDMxNC0wLjI0OCAwLTAuNDY3NS0wLjA0MzQtMC42NTg3LTAuMTMtMC4xOTEyLTAuMDg5Ni0wLjM0MDUtMC4yMzQ1LTAuNDQ4MS0wLjQzNDYtMC4xMDc1LTAuMjAwMS0wLjE2MTMtMC40NjYtMC4xNjEzLTAuNzk3NnYtNC43NjN6bTUuODM4NCA0Ljg5M3YtMy43MDU2aDEuMDg0M3Y0Ljg0ODFoLTEuMDIxNmwtMC4wNjI3LTEuMTQyNXptMC4xNTIzLTEuMDA4MiAwLjM2My0wLjAwODljMCAwLjMyNTUtMC4wMzU5IDAuNjI1OC0wLjEwNzYgMC45MDA2LTAuMDcxNyAwLjI3MTgtMC4xODIyIDAuNTA5My0wLjMzMTYgMC43MTI0LTAuMTQ5MyAwLjIwMDEtMC4zNDA1IDAuMzU3LTAuNTczNSAwLjQ3MDUtMC4yMzMgMC4xMTA1LTAuNTEyMyAwLjE2NTgtMC44Mzc5IDAuMTY1OC0wLjIzNiAwLTAuNDUyNS0wLjAzNDQtMC42NDk3LTAuMTAzMS0wLjE5NzEtMC4wNjg3LTAuMzY3NC0wLjE3NDctMC41MTA4LTAuMzE4MS0wLjE0MDQtMC4xNDM0LTAuMjQ5NC0wLjMzMDEtMC4zMjcxLTAuNTYwMS0wLjA3NzYtMC4yMy0wLjExNjUtMC41MDQ4LTAuMTE2NS0wLjgyNDV2LTMuMTMyaDEuMDc5OXYzLjE0MWMwIDAuMTc2MiAwLjAyMDkgMC4zMjQxIDAuMDYyNyAwLjQ0MzYgMC4wNDE4IDAuMTE2NSAwLjA5ODYgMC4yMTA2IDAuMTcwMyAwLjI4MjNzMC4xNTUzIDAuMTIyNCAwLjI1MDkgMC4xNTIzIDAuMTk3MSAwLjA0NDggMC4zMDQ3IDAuMDQ0OGMwLjMwNzcgMCAwLjU0OTYtMC4wNTk3IDAuNzI1OS0wLjE3OTIgMC4xNzkyLTAuMTIyNSAwLjMwNjEtMC4yODY4IDAuMzgwOC0wLjQ5MjkgMC4wNzc3LTAuMjA2MSAwLjExNjUtMC40Mzc2IDAuMTE2NS0wLjY5NDV6bTMuMjY2NC0xLjc3NDN2My45MjVoLTEuMDc5OHYtNC44NDgxaDEuMDMwNmwwLjA0OTIgMC45MjMxem0xLjQ4MzItMC45NTQ0LTllLTMgMS4wMDM2Yy0wLjA2NTctMC4wMTE5LTAuMTM3NC0wLjAyMDktMC4yMTUxLTAuMDI2OC0wLjA3NDctNmUtMyAtMC4xNDkzLTllLTMgLTAuMjI0LTllLTMgLTAuMTg1MiAwLTAuMzQ4IDAuMDI2OS0wLjQ4ODQgMC4wODA3LTAuMTQwNCAwLjA1MDctMC4yNTg0IDAuMTI1NC0wLjM1NCAwLjIyNC0wLjA5MjYgMC4wOTU2LTAuMTY0MyAwLjIxMjEtMC4yMTUxIDAuMzQ5NS0wLjA1MDcgMC4xMzc0LTAuMDgwNiAwLjI5MTItMC4wODk2IDAuNDYxNWwtMC4yNDY0IDAuMDE3OWMwLTAuMzA0NyAwLjAyOTktMC41ODcgMC4wODk2LTAuODQ2OCAwLjA1OTctMC4yNTk5IDAuMTQ5NC0wLjQ4ODQgMC4yNjg4LTAuNjg1NiAwLjEyMjUtMC4xOTcxIDAuMjc0OS0wLjM1MSAwLjQ1NzEtMC40NjE1IDAuMTg1Mi0wLjExMDUgMC4zOTg4LTAuMTY1OCAwLjY0MDctMC4xNjU4IDAuMDY1NyAwIDAuMTM1OSA2ZS0zIDAuMjEwNiAwLjAxNzkgMC4wNzc3IDAuMDEyIDAuMTM1OSAwLjAyNTQgMC4xNzQ4IDAuMDQwNHptMi44Njc2IDQuOTY5MWMtMC4zNTg1IDAtMC42ODI2LTAuMDU4My0wLjk3MjMtMC4xNzQ4LTAuMjg2OC0wLjExOTUtMC41MzE3LTAuMjg1My0wLjczNDgtMC40OTczLTAuMjAwMi0wLjIxMjEtMC4zNTQtMC40NjE2LTAuNDYxNi0wLjc0ODMtMC4xMDc1LTAuMjg2OC0wLjE2MTMtMC41OTYtMC4xNjEzLTAuOTI3NXYtMC4xNzkzYzAtMC4zNzkzIDAuMDU1My0wLjcyMjggMC4xNjU4LTEuMDMwNSAwLjExMDYtMC4zMDc3IDAuMjY0NC0wLjU3MDYgMC40NjE1LTAuNzg4NiAwLjE5NzItMC4yMjExIDAuNDMwMi0wLjM4OTggMC42OTktMC41MDYzIDAuMjY4OS0wLjExNjUgMC41NjAxLTAuMTc0OCAwLjg3MzgtMC4xNzQ4IDAuMzQ2NSAwIDAuNjQ5NyAwLjA1ODMgMC45MDk1IDAuMTc0OCAwLjI1OTkgMC4xMTY1IDAuNDc1IDAuMjgwOCAwLjY0NTMgMC40OTI4IDAuMTcyOSAwLjIwOTEgMC4zMDE5IDAuNDU4NiAwLjM4NDkgMC43NDgzIDAuMDg3IDAuMjg5OCAwLjEzIDAuNjA5NCAwLjEzIDAuOTU4OXYwLjQ2MTVoLTMuNzQ1NXYtMC43NzUyaDIuNjc5NHYtMC4wODUxYy0wLjAwNTktMC4xOTQyLTAuMDQ0OC0wLjM3NjQtMC4xMTY1LTAuNTQ2Ni0wLjA2ODctMC4xNzAzLTAuMTc0Ny0wLjMwNzctMC4zMTgxLTAuNDEyMy0wLjE0MzQtMC4xMDQ1LTAuMzM0NS0wLjE1NjgtMC41NzM1LTAuMTU2OC0wLjE3OTIgMC0wLjMzOTEgMC4wMzg4LTAuNDc5NSAwLjExNjUtMC4xMzc0IDAuMDc0Ny0wLjI1MjQgMC4xODM3LTAuMzQ1IDAuMzI3MXMtMC4xNjQzIDAuMzE2Ni0wLjIxNSAwLjUxOThjLTAuMDQ3OCAwLjIwMDEtMC4wNzE3IDAuNDI1Ni0wLjA3MTcgMC42NzY1djAuMTc5M2MwIDAuMjEyMSAwLjAyODMgMC40MDkyIDAuMDg1MSAwLjU5MTQgMC4wNTk3IDAuMTc5MyAwLjE0NjQgMC4zMzYxIDAuMjU5OSAwLjQ3MDVzMC4yNTA5IDAuMjQwNSAwLjQxMjIgMC4zMTgxYzAuMTYxMyAwLjA3NDcgMC4zNDUgMC4xMTIgMC41NTExIDAuMTEyIDAuMjU5OSAwIDAuNDkxNC0wLjA1MjIgMC42OTQ1LTAuMTU2OCAwLjIwMzItMC4xMDQ1IDAuMzc5NC0wLjI1MjQgMC41Mjg4LTAuNDQzNmwwLjU2ODggMC41NTEyYy0wLjEwNCAwLjE1MjMtMC4yNCAwLjI5ODctMC40MDc1IDAuNDM5MS0wLjE2NzMgMC4xMzc0LTAuMzcxOSAwLjI0OTQtMC42MTM5IDAuMzM2LTAuMjM5IDAuMDg2Ni0wLjUxNjggMC4xMy0wLjgzMzQgMC4xM3oiIGZpbGwtb3BhY2l0eT0iLjg3Ii8+CiAgIDxwYXRoIGQ9Im01MC4zNTYgMzYuNTk2djAuNjY4N2gtMi40NTY2di0wLjY2ODdoMi40NTY2em0tMi4yMjEzLTQuMjI0MnY0Ljg5MjloLTAuODQzNXYtNC44OTI5aDAuODQzNXptNC45ODU5IDQuMTYzN3YtMS43MzRjMC0wLjEzLTAuMDIzNi0wLjI0Mi0wLjA3MDYtMC4zMzYxLTAuMDQ3MS0wLjA5NDEtMC4xMTg3LTAuMTY2OS0wLjIxNTEtMC4yMTg0LTAuMDk0MS0wLjA1MTUtMC4yMTI4LTAuMDc3My0wLjM1NjItMC4wNzczLTAuMTMyMiAwLTAuMjQ2NCAwLjAyMjQtMC4zNDI4IDAuMDY3Mi0wLjA5NjMgMC4wNDQ4LTAuMTcxNCAwLjEwNTMtMC4yMjUxIDAuMTgxNS0wLjA1MzggMC4wNzYyLTAuMDgwNyAwLjE2MjQtMC4wODA3IDAuMjU4N2gtMC44MDY1YzAtMC4xNDMzIDAuMDM0Ny0wLjI4MjIgMC4xMDQyLTAuNDE2NyAwLjA2OTQtMC4xMzQ0IDAuMTcwMi0wLjI1NDIgMC4zMDI0LTAuMzU5NXMwLjI5MDEtMC4xODgyIDAuNDczOS0wLjI0ODdjMC4xODM3LTAuMDYwNSAwLjM4OTgtMC4wOTA3IDAuNjE4My0wLjA5MDcgMC4yNzMzIDAgMC41MTUzIDAuMDQ1OSAwLjcyNTkgMC4xMzc3IDAuMjEyOCAwLjA5MTkgMC4zNzk3IDAuMjMwOCAwLjUwMDcgMC40MTY3IDAuMTIzMiAwLjE4MzcgMC4xODQ4IDAuNDE0NSAwLjE4NDggMC42OTIzdjEuNjE2NGMwIDAuMTY1OCAwLjAxMTIgMC4zMTQ4IDAuMDMzNiAwLjQ0NyAwLjAyNDcgMC4xMjk5IDAuMDU5NCAwLjI0MyAwLjEwNDIgMC4zMzk0djAuMDUzN2gtMC44MzAxYy0wLjAzOC0wLjA4NzMtMC4wNjgzLTAuMTk4Mi0wLjA5MDctMC4zMzI2LTAuMDIwMi0wLjEzNjctMC4wMzAyLTAuMjY4OS0wLjAzMDItMC4zOTY2em0wLjExNzYtMS40ODIgMC4wMDY3IDAuNTAwN2gtMC41ODE0Yy0wLjE1MDEgMC0wLjI4MjMgMC4wMTQ2LTAuMzk2NSAwLjA0MzctMC4xMTQzIDAuMDI2OS0wLjIwOTUgMC4wNjcyLTAuMjg1NyAwLjEyMS0wLjA3NjEgMC4wNTM4LTAuMTMzMyAwLjExODctMC4xNzEzIDAuMTk0OS0wLjAzODEgMC4wNzYyLTAuMDU3MiAwLjE2MjQtMC4wNTcyIDAuMjU4OCAwIDAuMDk2MyAwLjAyMjQgMC4xODQ4IDAuMDY3MiAwLjI2NTUgMC4wNDQ4IDAuMDc4NCAwLjEwOTggMC4xNCAwLjE5NDkgMC4xODQ4IDAuMDg3NCAwLjA0NDggMC4xOTI3IDAuMDY3MiAwLjMxNTkgMC4wNjcyIDAuMTY1OCAwIDAuMzEwMy0wLjAzMzYgMC40MzM1LTAuMTAwOCAwLjEyNTUtMC4wNjk1IDAuMjI0MS0wLjE1MzUgMC4yOTU4LTAuMjUyMSAwLjA3MTctMC4xMDA4IDAuMTA5Ny0wLjE5NiAwLjExNDItMC4yODU2bDAuMjYyMiAwLjM1OTZjLTAuMDI2OSAwLjA5MTgtMC4wNzI5IDAuMTkwNC0wLjEzNzggMC4yOTU3LTAuMDY1IDAuMTA1My0wLjE1MDEgMC4yMDYxLTAuMjU1NCAwLjMwMjQtMC4xMDMxIDAuMDk0MS0wLjIyNzQgMC4xNzE0LTAuMzczIDAuMjMxOS0wLjE0MzQgMC4wNjA1LTAuMzA5MiAwLjA5MDgtMC40OTc0IDAuMDkwOC0wLjIzNzUgMC0wLjQ0OTItMC4wNDcxLTAuNjM1MS0wLjE0MTItMC4xODYtMC4wOTYzLTAuMzMxNi0wLjIyNTEtMC40MzY5LTAuMzg2NC0wLjEwNTMtMC4xNjM2LTAuMTU4LTAuMzQ4NC0wLjE1OC0wLjU1NDUgMC0wLjE5MjcgMC4wMzU5LTAuMzYzIDAuMTA3Ni0wLjUxMDggMC4wNzM5LTAuMTUwMSAwLjE4MTQtMC4yNzU2IDAuMzIyNi0wLjM3NjQgMC4xNDM0LTAuMTAwOCAwLjMxODEtMC4xNzcgMC41MjQyLTAuMjI4NSAwLjIwNjEtMC4wNTM4IDAuNDQxNC0wLjA4MDcgMC43MDU3LTAuMDgwN2gwLjYzNTJ6bTMuNzI1NyAxLjIyNjZjMC0wLjA4MDYtMC4wMjAyLTAuMTUzNC0wLjA2MDUtMC4yMTg0LTAuMDQwMy0wLjA2NzItMC4xMTc2LTAuMTI3Ny0wLjIzMTktMC4xODE1LTAuMTEyLTAuMDUzOC0wLjI3NzgtMC4xMDMtMC40OTczLTAuMTQ3OS0wLjE5MjctMC4wNDI1LTAuMzY5Ny0wLjA5MjktMC41MzEtMC4xNTEyLTAuMTU5MS0wLjA2MDUtMC4yOTU3LTAuMTMzMy0wLjQxLTAuMjE4NC0wLjExNDItMC4wODUxLTAuMjAyNy0wLjE4Ni0wLjI2NTUtMC4zMDI1LTAuMDYyNy0wLjExNjUtMC4wOTQxLTAuMjUwOS0wLjA5NDEtMC40MDMyIDAtMC4xNDc5IDAuMDMyNS0wLjI4NzkgMC4wOTc1LTAuNDIwMXMwLjE1NzktMC4yNDg3IDAuMjc4OS0wLjM0OTUgMC4yNjc3LTAuMTgwMyAwLjQ0MDItMC4yMzg2YzAuMTc0OC0wLjA1ODIgMC4zNjk3LTAuMDg3MyAwLjU4NDgtMC4wODczIDAuMzA0NyAwIDAuNTY1NyAwLjA1MTUgMC43ODMgMC4xNTQ1IDAuMjE5NSAwLjEwMDkgMC4zODc2IDAuMjM4NiAwLjUwNDEgMC40MTM0IDAuMTE2NSAwLjE3MjUgMC4xNzQ3IDAuMzY3NCAwLjE3NDcgMC41ODQ3aC0wLjgwOTljMC0wLjA5NjMtMC4wMjQ2LTAuMTg1OS0wLjA3MzktMC4yNjg4LTAuMDQ3MS0wLjA4NTItMC4xMTg4LTAuMTUzNS0wLjIxNTEtMC4yMDUtMC4wOTYzLTAuMDUzOC0wLjIxNzMtMC4wODA3LTAuMzYyOS0wLjA4MDctMC4xMzg5IDAtMC4yNTQzIDAuMDIyNC0wLjM0NjIgMC4wNjcyLTAuMDg5NiAwLjA0MjYtMC4xNTY4IDAuMDk4Ni0wLjIwMTYgMC4xNjgxLTAuMDQyNiAwLjA2OTQtMC4wNjM4IDAuMTQ1Ni0wLjA2MzggMC4yMjg1IDAgMC4wNjA1IDAuMDExMiAwLjExNTQgMC4wMzM2IDAuMTY0NiAwLjAyNDYgMC4wNDcxIDAuMDY0OSAwLjA5MDggMC4xMjA5IDAuMTMxMSAwLjA1NjEgMC4wMzgxIDAuMTMyMiAwLjA3MzkgMC4yMjg2IDAuMTA3NSAwLjA5ODUgMC4wMzM2IDAuMjIxOCAwLjA2NjEgMC4zNjk2IDAuMDk3NSAwLjI3NzggMC4wNTgyIDAuNTE2NCAwLjEzMzMgMC43MTU4IDAuMjI1MSAwLjIwMTYgMC4wODk3IDAuMzU2MiAwLjIwNjIgMC40NjM4IDAuMzQ5NSAwLjEwNzUgMC4xNDEyIDAuMTYxMyAwLjMyMDQgMC4xNjEzIDAuNTM3NyAwIDAuMTYxMy0wLjAzNDggMC4zMDkyLTAuMTA0MiAwLjQ0MzYtMC4wNjcyIDAuMTMyMi0wLjE2NTggMC4yNDc2LTAuMjk1NyAwLjM0NjItMC4xMyAwLjA5NjMtMC4yODU3IDAuMTcxMy0wLjQ2NzIgMC4yMjUxLTAuMTc5MiAwLjA1MzgtMC4zODA4IDAuMDgwNy0wLjYwNDggMC4wODA3LTAuMzI5NCAwLTAuNjA4My0wLjA1ODMtMC44MzY4LTAuMTc0OC0wLjIyODUtMC4xMTg3LTAuNDAyMi0wLjI3LTAuNTIwOS0wLjQ1MzctMC4xMTY1LTAuMTg1OS0wLjE3NDctMC4zNzg2LTAuMTc0Ny0wLjU3OGgwLjc4M2MwLjAwODkgMC4xNTAxIDAuMDUwNCAwLjI3IDAuMTI0MyAwLjM1OTYgMC4wNzYyIDAuMDg3NCAwLjE3MDMgMC4xNTEyIDAuMjgyMyAwLjE5MTYgMC4xMTQyIDAuMDM4IDAuMjMxOSAwLjA1NzEgMC4zNTI4IDAuMDU3MSAwLjE0NTcgMCAwLjI2NzgtMC4wMTkxIDAuMzY2My0wLjA1NzEgMC4wOTg2LTAuMDQwNCAwLjE3MzctMC4wOTQxIDAuMjI1Mi0wLjE2MTMgMC4wNTE1LTAuMDY5NSAwLjA3NzMtMC4xNDc5IDAuMDc3My0wLjIzNTN6bTMuMzEyMy0yLjY1MTR2MC41OTE0aC0yLjA0OTl2LTAuNTkxNGgyLjA0OTl6bS0xLjQ1ODQtMC44OTA2aDAuODA5OXYzLjUyMTljMCAwLjExMiAwLjAxNTYgMC4xOTgyIDAuMDQ3IDAuMjU4NyAwLjAzMzYgMC4wNTgzIDAuMDc5NSAwLjA5NzUgMC4xMzc4IDAuMTE3NiAwLjA1ODIgMC4wMjAyIDAuMTI2NiAwLjAzMDMgMC4yMDUgMC4wMzAzIDAuMDU2IDAgMC4xMDk4LTAuMDAzNCAwLjE2MTMtMC4wMTAxczAuMDkzLTAuMDEzNCAwLjEyNDMtMC4wMjAybDAuMDAzNCAwLjYxODRjLTAuMDY3MiAwLjAyMDEtMC4xNDU2IDAuMDM4MS0wLjIzNTMgMC4wNTM3LTAuMDg3MyAwLjAxNTctMC4xODgxIDAuMDIzNi0wLjMwMjQgMC4wMjM2LTAuMTg2IDAtMC4zNTA2LTAuMDMyNS0wLjQ5NC0wLjA5NzUtMC4xNDM0LTAuMDY3Mi0wLjI1NTQtMC4xNzU5LTAuMzM2MS0wLjMyNi0wLjA4MDYtMC4xNTAxLTAuMTIwOS0wLjM0OTUtMC4xMjA5LTAuNTk4MXYtMy41NzIzem02LjI3MTggMy42Njk3di0yLjc3OTFoMC44MTMzdjMuNjM2aC0wLjc2NjJsLTAuMDQ3MS0wLjg1Njl6bTAuMTE0My0wLjc1NjEgMC4yNzIyLTAuMDA2N2MwIDAuMjQ0Mi0wLjAyNjkgMC40NjkzLTAuMDgwNyAwLjY3NTQtMC4wNTM3IDAuMjAzOS0wLjEzNjYgMC4zODItMC4yNDg2IDAuNTM0NC0wLjExMjEgMC4xNTAxLTAuMjU1NCAwLjI2NzctMC40MzAyIDAuMzUyOC0wLjE3NDcgMC4wODI5LTAuMzg0MiAwLjEyNDQtMC42Mjg0IDAuMTI0NC0wLjE3NyAwLTAuMzM5NC0wLjAyNTgtMC40ODczLTAuMDc3My0wLjE0NzgtMC4wNTE2LTAuMjc1NS0wLjEzMTEtMC4zODMxLTAuMjM4Ni0wLjEwNTMtMC4xMDc2LTAuMTg3MS0wLjI0NzYtMC4yNDUzLTAuNDIwMS0wLjA1ODMtMC4xNzI1LTAuMDg3NC0wLjM3ODYtMC4wODc0LTAuNjE4M3YtMi4zNDloMC44MDk5djIuMzU1N2MwIDAuMTMyMiAwLjAxNTcgMC4yNDMxIDAuMDQ3MSAwLjMzMjcgMC4wMzEzIDAuMDg3NCAwLjA3MzkgMC4xNTc5IDAuMTI3NyAwLjIxMTcgMC4wNTM3IDAuMDUzOCAwLjExNjUgMC4wOTE4IDAuMTg4MSAwLjExNDMgMC4wNzE3IDAuMDIyNCAwLjE0NzkgMC4wMzM2IDAuMjI4NiAwLjAzMzYgMC4yMzA3IDAgMC40MTIyLTAuMDQ0OSAwLjU0NDQtMC4xMzQ1IDAuMTM0NC0wLjA5MTggMC4yMjk2LTAuMjE1IDAuMjg1Ni0wLjM2OTYgMC4wNTgzLTAuMTU0NiAwLjA4NzQtMC4zMjgyIDAuMDg3NC0wLjUyMDl6bTIuNDg1Ny0xLjMyNHY0LjMzNWgtMC44MDk5di01LjAzNGgwLjc0NmwwLjA2MzkgMC42OTl6bTIuMzY5MSAxLjA4NTR2MC4wNzA2YzAgMC4yNjQzLTAuMDMxMyAwLjUwOTctMC4wOTQxIDAuNzM1OS0wLjA2MDUgMC4yMjQxLTAuMTUxMiAwLjQyMDEtMC4yNzIyIDAuNTg4MS0wLjExODcgMC4xNjU4LTAuMjY1NSAwLjI5NDYtMC40NDAyIDAuMzg2NS0wLjE3NDggMC4wOTE4LTAuMzc2NCAwLjEzNzgtMC42MDQ5IDAuMTM3OC0wLjIyNjMgMC0wLjQyNDUtMC4wNDE1LTAuNTk0OC0wLjEyNDQtMC4xNjgtMC4wODUxLTAuMzEwMy0wLjIwNS0wLjQyNjgtMC4zNTk2LTAuMTE2NS0wLjE1NDUtMC4yMTA2LTAuMzM2LTAuMjgyMy0wLjU0NDQtMC4wNjk0LTAuMjEwNi0wLjExODctMC40NDEzLTAuMTQ3OC0wLjY5MjJ2LTAuMjcyMmMwLjAyOTEtMC4yNjY2IDAuMDc4NC0wLjUwODYgMC4xNDc4LTAuNzI1OSAwLjA3MTctMC4yMTczIDAuMTY1OC0wLjQwNDQgMC4yODIzLTAuNTYxMnMwLjI1ODgtMC4yNzc4IDAuNDI2OC0wLjM2MjljMC4xNjgtMC4wODUyIDAuMzY0LTAuMTI3NyAwLjU4ODEtMC4xMjc3IDAuMjI4NSAwIDAuNDMxMiAwLjA0NDggMC42MDgyIDAuMTM0NCAwLjE3NyAwLjA4NzMgMC4zMjYgMC4yMTI4IDAuNDQ3IDAuMzc2NCAwLjEyMSAwLjE2MTMgMC4yMTE3IDAuMzU2MiAwLjI3MjIgMC41ODQ3IDAuMDYwNSAwLjIyNjMgMC4wOTA3IDAuNDc4MyAwLjA5MDcgMC43NTYxem0tMC44MDk5IDAuMDcwNnYtMC4wNzA2YzAtMC4xNjgtMC4wMTU2LTAuMzIzNy0wLjA0Ny0wLjQ2NzEtMC4wMzE0LTAuMTQ1Ni0wLjA4MDctMC4yNzMzLTAuMTQ3OS0wLjM4MzFzLTAuMTUzNC0wLjE5NDktMC4yNTg3LTAuMjU1NGMtMC4xMDMxLTAuMDYyNy0wLjIyNzQtMC4wOTQxLTAuMzczMS0wLjA5NDEtMC4xNDMzIDAtMC4yNjY2IDAuMDI0Ni0wLjM2OTYgMC4wNzM5LTAuMTAzMSAwLjA0NzEtMC4xODkzIDAuMTEzMi0wLjI1ODggMC4xOTgzLTAuMDY5NCAwLjA4NTEtMC4xMjMyIDAuMTg0OC0wLjE2MTMgMC4yOTkxLTAuMDM4MSAwLjExMi0wLjA2NDkgMC4yMzQxLTAuMDgwNiAwLjM2NjN2MC42NTE5YzAuMDI2OSAwLjE2MTMgMC4wNzI4IDAuMzA5MiAwLjEzNzggMC40NDM2IDAuMDY0OSAwLjEzNDQgMC4xNTY4IDAuMjQyIDAuMjc1NSAwLjMyMjYgMC4xMjEgMC4wNzg0IDAuMjc1NiAwLjExNzYgMC40NjM4IDAuMTE3NiAwLjE0NTYgMCAwLjI2OTktMC4wMzEzIDAuMzczLTAuMDk0MSAwLjEwMy0wLjA2MjcgMC4xODcxLTAuMTQ4OSAwLjI1Mi0wLjI1ODcgMC4wNjcyLTAuMTEyIDAuMTE2NS0wLjI0MDkgMC4xNDc5LTAuMzg2NXMwLjA0Ny0wLjMwMDIgMC4wNDctMC40NjM3em0zLjg2MDIgMS4wMjgzdi00LjQwOWgwLjgxMzJ2NS4xNjE3aC0wLjczNTlsLTAuMDc3My0wLjc1Mjd6bS0yLjM2NTktMS4wMjV2LTAuMDcwNWMwLTAuMjc1NiAwLjAzMjUtMC41MjY1IDAuMDk3NS0wLjc1MjggMC4wNjUtMC4yMjg1IDAuMTU5MS0wLjQyNDUgMC4yODIzLTAuNTg4MSAwLjEyMzItMC4xNjU4IDAuMjczMy0wLjI5MjQgMC40NTAzLTAuMzc5NyAwLjE3Ny0wLjA4OTYgMC4zNzY0LTAuMTM0NCAwLjU5ODItMC4xMzQ0IDAuMjE5NSAwIDAuNDEyMiAwLjA0MjUgMC41NzggMC4xMjc3IDAuMTY1OCAwLjA4NTEgMC4zMDY5IDAuMjA3MiAwLjQyMzQgMC4zNjYyIDAuMTE2NSAwLjE1NjkgMC4yMDk1IDAuMzQ1MSAwLjI3ODkgMC41NjQ2IDAuMDY5NSAwLjIxNzMgMC4xMTg4IDAuNDU5MyAwLjE0NzkgMC43MjU5djAuMjI1MWMtMC4wMjkxIDAuMjU5OS0wLjA3ODQgMC40OTc0LTAuMTQ3OSAwLjcxMjUtMC4wNjk0IDAuMjE1LTAuMTYyNCAwLjQwMS0wLjI3ODkgMC41NTc4cy0wLjI1ODggMC4yNzc4LTAuNDI2OCAwLjM2M2MtMC4xNjU4IDAuMDg1MS0wLjM1OTYgMC4xMjc3LTAuNTgxMyAwLjEyNzctMC4yMTk2IDAtMC40MTc5LTAuMDQ2LTAuNTk0OS0wLjEzNzgtMC4xNzQ3LTAuMDkxOS0wLjMyMzctMC4yMjA3LTAuNDQ2OS0wLjM4NjVzLTAuMjE3My0wLjM2MDctMC4yODIzLTAuNTg0N2MtMC4wNjUtMC4yMjYzLTAuMDk3NS0wLjQ3MTYtMC4wOTc1LTAuNzM2em0wLjgwOTktMC4wNzA1djAuMDcwNWMwIDAuMTY1OCAwLjAxNDYgMC4zMjA0IDAuMDQzNyAwLjQ2MzggMC4wMzE0IDAuMTQzNCAwLjA3OTYgMC4yNjk5IDAuMTQ0NSAwLjM3OTcgMC4wNjUgMC4xMDc2IDAuMTQ5IDAuMTkyNyAwLjI1MjEgMC4yNTU0IDAuMTA1MyAwLjA2MDUgMC4yMzA3IDAuMDkwOCAwLjM3NjMgMC4wOTA4IDAuMTgzOCAwIDAuMzM1LTAuMDQwNCAwLjQ1MzctMC4xMjEgMC4xMTg4LTAuMDgwNyAwLjIxMTctMC4xODkzIDAuMjc4OS0wLjMyNiAwLjA2OTUtMC4xMzg5IDAuMTE2NS0wLjI5MzUgMC4xNDEyLTAuNDYzN3YtMC42MDgzYy0wLjAxMzUtMC4xMzIyLTAuMDQxNS0wLjI1NTQtMC4wODQtMC4zNjk3LTAuMDQwNC0wLjExNDItMC4wOTUyLTAuMjEzOS0wLjE2NDctMC4yOTktMC4wNjk1LTAuMDg3NC0wLjE1NTctMC4xNTQ2LTAuMjU4OC0wLjIwMTctMC4xMDA4LTAuMDQ5My0wLjIyMDYtMC4wNzM5LTAuMzU5NS0wLjA3MzktMC4xNDc5IDAtMC4yNzM0IDAuMDMxNC0wLjM3NjQgMC4wOTQxLTAuMTAzMSAwLjA2MjctMC4xODgyIDAuMTQ5LTAuMjU1NCAwLjI1ODctMC4wNjUgMC4xMDk4LTAuMTEzMiAwLjIzNzUtMC4xNDQ1IDAuMzgzMS0wLjAzMTQgMC4xNDU3LTAuMDQ3MSAwLjMwMTQtMC4wNDcxIDAuNDY3MnptNS40MDk0IDEuMTE5di0xLjczNGMwLTAuMTMtMC4wMjM2LTAuMjQyLTAuMDcwNi0wLjMzNjEtMC4wNDcxLTAuMDk0MS0wLjExODgtMC4xNjY5LTAuMjE1MS0wLjIxODQtMC4wOTQxLTAuMDUxNS0wLjIxMjgtMC4wNzczLTAuMzU2Mi0wLjA3NzMtMC4xMzIyIDAtMC4yNDY0IDAuMDIyNC0wLjM0MjggMC4wNjcyLTAuMDk2MyAwLjA0NDgtMC4xNzE0IDAuMTA1My0wLjIyNTEgMC4xODE1LTAuMDUzOCAwLjA3NjItMC4wODA3IDAuMTYyNC0wLjA4MDcgMC4yNTg3aC0wLjgwNjVjMC0wLjE0MzMgMC4wMzQ3LTAuMjgyMiAwLjEwNDItMC40MTY3IDAuMDY5NC0wLjEzNDQgMC4xNzAyLTAuMjU0MiAwLjMwMjQtMC4zNTk1czAuMjkwMS0wLjE4ODIgMC40NzM4LTAuMjQ4N2MwLjE4MzgtMC4wNjA1IDAuMzg5OS0wLjA5MDcgMC42MTg0LTAuMDkwNyAwLjI3MzMgMCAwLjUxNTMgMC4wNDU5IDAuNzI1OSAwLjEzNzcgMC4yMTI4IDAuMDkxOSAwLjM3OTcgMC4yMzA4IDAuNTAwNyAwLjQxNjcgMC4xMjMyIDAuMTgzNyAwLjE4NDggMC40MTQ1IDAuMTg0OCAwLjY5MjN2MS42MTY0YzAgMC4xNjU4IDAuMDExMiAwLjMxNDggMC4wMzM2IDAuNDQ3IDAuMDI0NyAwLjEyOTkgMC4wNTk0IDAuMjQzIDAuMTA0MiAwLjMzOTR2MC4wNTM3aC0wLjgzMDFjLTAuMDM4LTAuMDg3My0wLjA2ODMtMC4xOTgyLTAuMDkwNy0wLjMzMjYtMC4wMjAyLTAuMTM2Ny0wLjAzMDItMC4yNjg5LTAuMDMwMi0wLjM5NjZ6bTAuMTE3Ni0xLjQ4MiAwLjAwNjcgMC41MDA3aC0wLjU4MTRjLTAuMTUwMSAwLTAuMjgyMyAwLjAxNDYtMC4zOTY1IDAuMDQzNy0wLjExNDMgMC4wMjY5LTAuMjA5NSAwLjA2NzItMC4yODU3IDAuMTIxLTAuMDc2MSAwLjA1MzgtMC4xMzMzIDAuMTE4Ny0wLjE3MTMgMC4xOTQ5LTAuMDM4MSAwLjA3NjItMC4wNTcyIDAuMTYyNC0wLjA1NzIgMC4yNTg4IDAgMC4wOTYzIDAuMDIyNCAwLjE4NDggMC4wNjcyIDAuMjY1NSAwLjA0NDggMC4wNzg0IDAuMTA5OCAwLjE0IDAuMTk0OSAwLjE4NDggMC4wODc0IDAuMDQ0OCAwLjE5MjcgMC4wNjcyIDAuMzE1OSAwLjA2NzIgMC4xNjU4IDAgMC4zMTAzLTAuMDMzNiAwLjQzMzUtMC4xMDA4IDAuMTI1NS0wLjA2OTUgMC4yMjQxLTAuMTUzNSAwLjI5NTgtMC4yNTIxIDAuMDcxNy0wLjEwMDggMC4xMDk3LTAuMTk2IDAuMTE0Mi0wLjI4NTZsMC4yNjIxIDAuMzU5NmMtMC4wMjY4IDAuMDkxOC0wLjA3MjggMC4xOTA0LTAuMTM3NyAwLjI5NTctMC4wNjUgMC4xMDUzLTAuMTUwMSAwLjIwNjEtMC4yNTU0IDAuMzAyNC0wLjEwMzEgMC4wOTQxLTAuMjI3NCAwLjE3MTQtMC4zNzMxIDAuMjMxOS0wLjE0MzMgMC4wNjA1LTAuMzA5MSAwLjA5MDgtMC40OTczIDAuMDkwOC0wLjIzNzUgMC0wLjQ0OTItMC4wNDcxLTAuNjM1MS0wLjE0MTItMC4xODYtMC4wOTYzLTAuMzMxNi0wLjIyNTEtMC40MzY5LTAuMzg2NC0wLjEwNTMtMC4xNjM2LTAuMTU4LTAuMzQ4NC0wLjE1OC0wLjU1NDUgMC0wLjE5MjcgMC4wMzU5LTAuMzYzIDAuMTA3Ni0wLjUxMDggMC4wNzM5LTAuMTUwMSAwLjE4MTQtMC4yNzU2IDAuMzIyNi0wLjM3NjQgMC4xNDM0LTAuMTAwOCAwLjMxODEtMC4xNzcgMC41MjQyLTAuMjI4NSAwLjIwNjEtMC4wNTM4IDAuNDQxNC0wLjA4MDcgMC43MDU3LTAuMDgwN2gwLjYzNTJ6bTMuMzUyNy0xLjQyNDh2MC41OTE0aC0yLjA1di0wLjU5MTRoMi4wNXptLTEuNDU4NS0wLjg5MDZoMC44MDk5djMuNTIxOWMwIDAuMTEyIDAuMDE1NyAwLjE5ODIgMC4wNDcgMC4yNTg3IDAuMDMzNiAwLjA1ODMgMC4wNzk2IDAuMDk3NSAwLjEzNzggMC4xMTc2IDAuMDU4MyAwLjAyMDIgMC4xMjY2IDAuMDMwMyAwLjIwNSAwLjAzMDMgMC4wNTYgMCAwLjEwOTgtMC4wMDM0IDAuMTYxMy0wLjAxMDFzMC4wOTMtMC4wMTM0IDAuMTI0My0wLjAyMDJsMC4wMDM0IDAuNjE4NGMtMC4wNjcyIDAuMDIwMS0wLjE0NTYgMC4wMzgxLTAuMjM1MiAwLjA1MzctMC4wODc0IDAuMDE1Ny0wLjE4ODIgMC4wMjM2LTAuMzAyNSAwLjAyMzYtMC4xODU5IDAtMC4zNTA2LTAuMDMyNS0wLjQ5NC0wLjA5NzUtMC4xNDM0LTAuMDY3Mi0wLjI1NTQtMC4xNzU5LTAuMzM2LTAuMzI2LTAuMDgwNy0wLjE1MDEtMC4xMjEtMC4zNDk1LTAuMTIxLTAuNTk4MXYtMy41NzIzem0zLjgyOTkgNC41OTM5Yy0wLjI2ODkgMC0wLjUxMi0wLjA0MzctMC43MjkzLTAuMTMxMS0wLjIxNS0wLjA4OTYtMC4zOTg3LTAuMjE0LTAuNTUxMS0wLjM3My0wLjE1MDEtMC4xNTkxLTAuMjY1NS0wLjM0NjItMC4zNDYxLTAuNTYxMi0wLjA4MDctMC4yMTUxLTAuMTIxLTAuNDQ3LTAuMTIxLTAuNjk1N3YtMC4xMzQ0YzAtMC4yODQ1IDAuMDQxNC0wLjU0MjEgMC4xMjQzLTAuNzcyOXMwLjE5ODMtMC40Mjc5IDAuMzQ2Mi0wLjU5MTRjMC4xNDc4LTAuMTY1OCAwLjMyMjYtMC4yOTI0IDAuNTI0Mi0wLjM3OThzMC40MjAxLTAuMTMxIDAuNjU1My0wLjEzMWMwLjI1OTkgMCAwLjQ4NzMgMC4wNDM2IDAuNjgyMiAwLjEzMXMwLjM1NjIgMC4yMTA2IDAuNDgzOSAwLjM2OTdjMC4xMyAwLjE1NjggMC4yMjYzIDAuMzQzOSAwLjI4OSAwLjU2MTIgMC4wNjUgMC4yMTczIDAuMDk3NSAwLjQ1NyAwLjA5NzUgMC43MTkxdjAuMzQ2MmgtMi44MDk0di0wLjU4MTRoMi4wMDk2di0wLjA2MzljLTAuMDA0NS0wLjE0NTYtMC4wMzM2LTAuMjgyMi0wLjA4NzQtMC40MDk5LTAuMDUxNS0wLjEyNzctMC4xMzExLTAuMjMwOC0wLjIzODYtMC4zMDkycy0wLjI1MDktMC4xMTc2LTAuNDMwMS0wLjExNzZjLTAuMTM0NSAwLTAuMjU0MyAwLjAyOTEtMC4zNTk2IDAuMDg3My0wLjEwMzEgMC4wNTYxLTAuMTg5MyAwLjEzNzgtMC4yNTg4IDAuMjQ1NC0wLjA2OTQgMC4xMDc1LTAuMTIzMiAwLjIzNzQtMC4xNjEzIDAuMzg5OC0wLjAzNTggMC4xNTAxLTAuMDUzOCAwLjMxOTItMC4wNTM4IDAuNTA3NHYwLjEzNDRjMCAwLjE1OTEgMC4wMjEzIDAuMzA3IDAuMDYzOSAwLjQ0MzYgMC4wNDQ4IDAuMTM0NSAwLjEwOTggMC4yNTIxIDAuMTk0OSAwLjM1MjlzMC4xODgyIDAuMTgwMyAwLjMwOTIgMC4yMzg2YzAuMTIwOSAwLjA1NiAwLjI1ODcgMC4wODQgMC40MTMzIDAuMDg0IDAuMTk0OSAwIDAuMzY4Ni0wLjAzOTIgMC41MjA5LTAuMTE3NnMwLjI4NDUtMC4xODkzIDAuMzk2NS0wLjMzMjdsMC40MjY4IDAuNDEzM2MtMC4wNzg0IDAuMTE0My0wLjE4MDMgMC4yMjQxLTAuMzA1OCAwLjMyOTQtMC4xMjU0IDAuMTAzLTAuMjc4OSAwLjE4Ny0wLjQ2MDQgMC4yNTItMC4xNzkyIDAuMDY1LTAuMzg3NiAwLjA5NzUtMC42MjUgMC4wOTc1em02LjI1MTctNC45Nzd2NC45MDk3aC0wLjgwOTl2LTMuOTQ4NmwtMS4xOTk3IDAuNDA2N3YtMC42Njg4bDEuOTEyMS0wLjY5OWgwLjA5NzV6bTQuMTA4OCA0LjE1N3YtNC40MDloMC44MTMydjUuMTYxN2gtMC43MzU5bC0wLjA3NzMtMC43NTI3em0tMi4zNjU4LTEuMDI1di0wLjA3MDVjMC0wLjI3NTYgMC4wMzI0LTAuNTI2NSAwLjA5NzQtMC43NTI4IDAuMDY1LTAuMjI4NSAwLjE1OTEtMC40MjQ1IDAuMjgyMy0wLjU4ODEgMC4xMjMyLTAuMTY1OCAwLjI3MzMtMC4yOTI0IDAuNDUwMy0wLjM3OTcgMC4xNzctMC4wODk2IDAuMzc2NC0wLjEzNDQgMC41OTgyLTAuMTM0NCAwLjIxOTUgMCAwLjQxMjIgMC4wNDI1IDAuNTc4IDAuMTI3NyAwLjE2NTggMC4wODUxIDAuMzA2OSAwLjIwNzIgMC40MjM0IDAuMzY2MiAwLjExNjUgMC4xNTY5IDAuMjA5NSAwLjM0NTEgMC4yNzg5IDAuNTY0NiAwLjA2OTUgMC4yMTczIDAuMTE4OCAwLjQ1OTMgMC4xNDc5IDAuNzI1OXYwLjIyNTFjLTAuMDI5MSAwLjI1OTktMC4wNzg0IDAuNDk3NC0wLjE0NzkgMC43MTI1LTAuMDY5NCAwLjIxNS0wLjE2MjQgMC40MDEtMC4yNzg5IDAuNTU3OHMtMC4yNTg3IDAuMjc3OC0wLjQyNjggMC4zNjNjLTAuMTY1OCAwLjA4NTEtMC4zNTk1IDAuMTI3Ny0wLjU4MTMgMC4xMjc3LTAuMjE5NiAwLTAuNDE3OS0wLjA0Ni0wLjU5NDktMC4xMzc4LTAuMTc0Ny0wLjA5MTktMC4zMjM3LTAuMjIwNy0wLjQ0NjktMC4zODY1cy0wLjIxNzMtMC4zNjA3LTAuMjgyMy0wLjU4NDdjLTAuMDY1LTAuMjI2My0wLjA5NzQtMC40NzE2LTAuMDk3NC0wLjczNnptMC44MDk4LTAuMDcwNXYwLjA3MDVjMCAwLjE2NTggMC4wMTQ2IDAuMzIwNCAwLjA0MzcgMC40NjM4IDAuMDMxNCAwLjE0MzQgMC4wNzk2IDAuMjY5OSAwLjE0NDUgMC4zNzk3IDAuMDY1IDAuMTA3NiAwLjE0OSAwLjE5MjcgMC4yNTIxIDAuMjU1NCAwLjEwNTMgMC4wNjA1IDAuMjMwNyAwLjA5MDggMC4zNzYzIDAuMDkwOCAwLjE4MzggMCAwLjMzNS0wLjA0MDQgMC40NTM3LTAuMTIxIDAuMTE4OC0wLjA4MDcgMC4yMTE3LTAuMTg5MyAwLjI3ODktMC4zMjYgMC4wNjk1LTAuMTM4OSAwLjExNjUtMC4yOTM1IDAuMTQxMi0wLjQ2Mzd2LTAuNjA4M2MtMC4wMTM1LTAuMTMyMi0wLjA0MTUtMC4yNTU0LTAuMDg0LTAuMzY5Ny0wLjA0MDQtMC4xMTQyLTAuMDk1Mi0wLjIxMzktMC4xNjQ3LTAuMjk5LTAuMDY5NC0wLjA4NzQtMC4xNTU3LTAuMTU0Ni0wLjI1ODgtMC4yMDE3LTAuMTAwOC0wLjA0OTMtMC4yMjA2LTAuMDczOS0wLjM1OTUtMC4wNzM5LTAuMTQ3OSAwLTAuMjczNCAwLjAzMTQtMC4zNzY0IDAuMDk0MS0wLjEwMzEgMC4wNjI3LTAuMTg4MiAwLjE0OS0wLjI1NTQgMC4yNTg3LTAuMDY1IDAuMTA5OC0wLjExMzEgMC4yMzc1LTAuMTQ0NSAwLjM4MzEtMC4wMzE0IDAuMTQ1Ny0wLjA0NzEgMC4zMDE0LTAuMDQ3MSAwLjQ2NzJ6bTcuMjY2NiAxLjExOXYtMS43MzRjMC0wLjEzLTAuMDIzNS0wLjI0Mi0wLjA3MDYtMC4zMzYxLTAuMDQ3LTAuMDk0MS0wLjExODctMC4xNjY5LTAuMjE1LTAuMjE4NC0wLjA5NDEtMC4wNTE1LTAuMjEyOS0wLjA3NzMtMC4zNTYyLTAuMDc3My0wLjEzMjIgMC0wLjI0NjUgMC4wMjI0LTAuMzQyOCAwLjA2NzItMC4wOTY0IDAuMDQ0OC0wLjE3MTQgMC4xMDUzLTAuMjI1MiAwLjE4MTUtMC4wNTM3IDAuMDc2Mi0wLjA4MDYgMC4xNjI0LTAuMDgwNiAwLjI1ODdoLTAuODA2NmMwLTAuMTQzMyAwLjAzNDgtMC4yODIyIDAuMTA0Mi0wLjQxNjcgMC4wNjk1LTAuMTM0NCAwLjE3MDMtMC4yNTQyIDAuMzAyNS0wLjM1OTUgMC4xMzIxLTAuMTA1MyAwLjI5MDEtMC4xODgyIDAuNDczOC0wLjI0ODdzMC4zODk4LTAuMDkwNyAwLjYxODMtMC4wOTA3YzAuMjczNCAwIDAuNTE1MyAwLjA0NTkgMC43MjU5IDAuMTM3NyAwLjIxMjggMC4wOTE5IDAuMzc5OCAwLjIzMDggMC41MDA3IDAuNDE2NyAwLjEyMzIgMC4xODM3IDAuMTg0OSAwLjQxNDUgMC4xODQ5IDAuNjkyM3YxLjYxNjRjMCAwLjE2NTggMC4wMTEyIDAuMzE0OCAwLjAzMzYgMC40NDcgMC4wMjQ2IDAuMTI5OSAwLjA1OTMgMC4yNDMgMC4xMDQxIDAuMzM5NHYwLjA1MzdoLTAuODNjLTAuMDM4MS0wLjA4NzMtMC4wNjgzLTAuMTk4Mi0wLjA5MDctMC4zMzI2LTAuMDIwMi0wLjEzNjctMC4wMzAzLTAuMjY4OS0wLjAzMDMtMC4zOTY2em0wLjExNzYtMS40ODIgMC4wMDY4IDAuNTAwN2gtMC41ODE0Yy0wLjE1MDEgMC0wLjI4MjMgMC4wMTQ2LTAuMzk2NiAwLjA0MzctMC4xMTQyIDAuMDI2OS0wLjIwOTQgMC4wNjcyLTAuMjg1NiAwLjEyMXMtMC4xMzMzIDAuMTE4Ny0wLjE3MTQgMC4xOTQ5LTAuMDU3MSAwLjE2MjQtMC4wNTcxIDAuMjU4OGMwIDAuMDk2MyAwLjAyMjQgMC4xODQ4IDAuMDY3MiAwLjI2NTUgMC4wNDQ4IDAuMDc4NCAwLjEwOTggMC4xNCAwLjE5NDkgMC4xODQ4IDAuMDg3NCAwLjA0NDggMC4xOTI3IDAuMDY3MiAwLjMxNTkgMC4wNjcyIDAuMTY1OCAwIDAuMzEwMy0wLjAzMzYgMC40MzM1LTAuMTAwOCAwLjEyNTUtMC4wNjk1IDAuMjI0LTAuMTUzNSAwLjI5NTctMC4yNTIxIDAuMDcxNy0wLjEwMDggMC4xMDk4LTAuMTk2IDAuMTE0My0wLjI4NTZsMC4yNjIxIDAuMzU5NmMtMC4wMjY5IDAuMDkxOC0wLjA3MjggMC4xOTA0LTAuMTM3OCAwLjI5NTdzLTAuMTUwMSAwLjIwNjEtMC4yNTU0IDAuMzAyNGMtMC4xMDMgMC4wOTQxLTAuMjI3NCAwLjE3MTQtMC4zNzMgMC4yMzE5LTAuMTQzNCAwLjA2MDUtMC4zMDkyIDAuMDkwOC0wLjQ5NzQgMC4wOTA4LTAuMjM3NCAwLTAuNDQ5MS0wLjA0NzEtMC42MzUxLTAuMTQxMi0wLjE4NTktMC4wOTYzLTAuMzMxNi0wLjIyNTEtMC40MzY5LTAuMzg2NC0wLjEwNTMtMC4xNjM2LTAuMTU3OS0wLjM0ODQtMC4xNTc5LTAuNTU0NSAwLTAuMTkyNyAwLjAzNTgtMC4zNjMgMC4xMDc1LTAuNTEwOCAwLjA3NC0wLjE1MDEgMC4xODE1LTAuMjc1NiAwLjMyMjYtMC4zNzY0IDAuMTQzNC0wLjEwMDggMC4zMTgyLTAuMTc3IDAuNTI0My0wLjIyODUgMC4yMDYxLTAuMDUzOCAwLjQ0MTMtMC4wODA3IDAuNzA1Ny0wLjA4MDdoMC42MzUxem00LjAxNDktMS40MjQ4aDAuNzM2djMuNTM1MmMwIDAuMzI3MS0wLjA3IDAuNjA0OS0wLjIwOSAwLjgzMzQtMC4xMzggMC4yMjg2LTAuMzMyIDAuNDAyMi0wLjU4MSAwLjUyMDktMC4yNDkgMC4xMjEtMC41MzYgMC4xODE1LTAuODY0IDAuMTgxNS0wLjEzOCAwLTAuMjkzLTAuMDIwMi0wLjQ2My0wLjA2MDUtMC4xNjgtMC4wNDAzLTAuMzMyLTAuMTA1My0wLjQ5MS0wLjE5NDktMC4xNTctMC4wODc0LTAuMjg4LTAuMjAyOC0wLjM5My0wLjM0NjFsMC4zOC0wLjQ3NzJjMC4xMyAwLjE1NDUgMC4yNzMgMC4yNjc3IDAuNDMgMC4zMzk0czAuMzIxIDAuMTA3NSAwLjQ5NCAwLjEwNzVjMC4xODYgMCAwLjM0NC0wLjAzNDcgMC40NzQtMC4xMDQyIDAuMTMyLTAuMDY3MiAwLjIzNC0wLjE2NjkgMC4zMDUtMC4yOTkgMC4wNzItMC4xMzIyIDAuMTA4LTAuMjkzNSAwLjEwOC0wLjQ4NHYtMi43Mjg3bDAuMDc0LTAuODIzM3ptLTIuNDcgMS44NTgzdi0wLjA3MDVjMC0wLjI3NTYgMC4wMzMtMC41MjY1IDAuMTAxLTAuNzUyOCAwLjA2Ny0wLjIyODUgMC4xNjMtMC40MjQ1IDAuMjg5LTAuNTg4MSAwLjEyNS0wLjE2NTggMC4yNzctMC4yOTI0IDAuNDU3LTAuMzc5NyAwLjE3OS0wLjA4OTYgMC4zODItMC4xMzQ0IDAuNjA4LTAuMTM0NCAwLjIzNSAwIDAuNDM2IDAuMDQyNSAwLjYwMSAwLjEyNzcgMC4xNjkgMC4wODUxIDAuMzA5IDAuMjA3MiAwLjQyMSAwLjM2NjIgMC4xMTIgMC4xNTY5IDAuMTk5IDAuMzQ1MSAwLjI2MiAwLjU2NDYgMC4wNjUgMC4yMTczIDAuMTEzIDAuNDU5MyAwLjE0NCAwLjcyNTl2MC4yMjUxYy0wLjAyOSAwLjI1OTktMC4wNzggMC40OTc0LTAuMTQ4IDAuNzEyNS0wLjA2OSAwLjIxNS0wLjE2MSAwLjQwMS0wLjI3NSAwLjU1NzgtMC4xMTUgMC4xNTY4LTAuMjU2IDAuMjc3OC0wLjQyNCAwLjM2My0wLjE2NSAwLjA4NTEtMC4zNjEgMC4xMjc3LTAuNTg4IDAuMTI3Ny0wLjIyMiAwLTAuNDIyLTAuMDQ2LTAuNjAxLTAuMTM3OC0wLjE3Ny0wLjA5MTktMC4zMy0wLjIyMDctMC40NTctMC4zODY1LTAuMTI2LTAuMTY1OC0wLjIyMi0wLjM2MDctMC4yODktMC41ODQ3LTAuMDY4LTAuMjI2My0wLjEwMS0wLjQ3MTYtMC4xMDEtMC43MzZ6bTAuODEtMC4wNzA1djAuMDcwNWMwIDAuMTY1OCAwLjAxNSAwLjMyMDQgMC4wNDcgMC40NjM4IDAuMDMzIDAuMTQzNCAwLjA4NCAwLjI2OTkgMC4xNTEgMC4zNzk3IDAuMDY5IDAuMTA3NiAwLjE1NyAwLjE5MjcgMC4yNjIgMC4yNTU0IDAuMTA4IDAuMDYwNSAwLjIzNCAwLjA5MDggMC4zOCAwLjA5MDggMC4xOSAwIDAuMzQ2LTAuMDQwNCAwLjQ2Ny0wLjEyMSAwLjEyMy0wLjA4MDcgMC4yMTctMC4xODkzIDAuMjgyLTAuMzI2IDAuMDY3LTAuMTM4OSAwLjExNS0wLjI5MzUgMC4xNDEtMC40NjM3di0wLjYwODNjLTAuMDEzLTAuMTMyMi0wLjA0MS0wLjI1NTQtMC4wODQtMC4zNjk3LTAuMDQtMC4xMTQyLTAuMDk1LTAuMjEzOS0wLjE2NC0wLjI5OS0wLjA3LTAuMDg3NC0wLjE1Ny0wLjE1NDYtMC4yNjItMC4yMDE3LTAuMTA2LTAuMDQ5My0wLjIzLTAuMDczOS0wLjM3My0wLjA3MzktMC4xNDYgMC0wLjI3MyAwLjAzMTQtMC4zOCAwLjA5NDEtMC4xMDggMC4wNjI3LTAuMTk2IDAuMTQ5LTAuMjY2IDAuMjU4Ny0wLjA2NyAwLjEwOTgtMC4xMTcgMC4yMzc1LTAuMTUxIDAuMzgzMS0wLjAzMyAwLjE0NTctMC4wNSAwLjMwMTQtMC4wNSAwLjQ2NzJ6bTMuMjI1IDAuMDcwNXYtMC4wNzczYzAtMC4yNjIxIDAuMDM4LTAuNTA1MiAwLjExNC0wLjcyOTIgMC4wNzYtMC4yMjYzIDAuMTg2LTAuNDIyMyAwLjMyOS0wLjU4ODEgMC4xNDYtMC4xNjggMC4zMjMtMC4yOTggMC41MzEtMC4zODk4IDAuMjExLTAuMDk0MSAwLjQ0OC0wLjE0MTEgMC43MTMtMC4xNDExIDAuMjY2IDAgMC41MDQgMC4wNDcgMC43MTIgMC4xNDExIDAuMjExIDAuMDkxOCAwLjM4OSAwLjIyMTggMC41MzQgMC4zODk4IDAuMTQ2IDAuMTY1OCAwLjI1NyAwLjM2MTggMC4zMzMgMC41ODgxIDAuMDc2IDAuMjI0IDAuMTE0IDAuNDY3MSAwLjExNCAwLjcyOTJ2MC4wNzczYzAgMC4yNjIyLTAuMDM4IDAuNTA1Mi0wLjExNCAwLjcyOTMtMC4wNzYgMC4yMjQtMC4xODcgMC40Mi0wLjMzMyAwLjU4ODEtMC4xNDUgMC4xNjU3LTAuMzIyIDAuMjk1Ny0wLjUzMSAwLjM4OTgtMC4yMDggMC4wOTE4LTAuNDQ0IDAuMTM3OC0wLjcwOSAwLjEzNzgtMC4yNjYgMC0wLjUwNS0wLjA0Ni0wLjcxNS0wLjEzNzgtMC4yMDktMC4wOTQxLTAuMzg2LTAuMjI0MS0wLjUzMS0wLjM4OTgtMC4xNDYtMC4xNjgxLTAuMjU3LTAuMzY0MS0wLjMzMy0wLjU4ODEtMC4wNzYtMC4yMjQxLTAuMTE0LTAuNDY3MS0wLjExNC0wLjcyOTN6bTAuODEtMC4wNzczdjAuMDc3M2MwIDAuMTYzNiAwLjAxNiAwLjMxODIgMC4wNSAwLjQ2MzhzMC4wODYgMC4yNzMzIDAuMTU4IDAuMzgzMSAwLjE2NCAwLjE5NiAwLjI3NiAwLjI1ODdjMC4xMTIgMC4wNjI4IDAuMjQ1IDAuMDk0MSAwLjM5OSAwLjA5NDEgMC4xNTEgMCAwLjI4LTAuMDMxMyAwLjM5LTAuMDk0MSAwLjExMi0wLjA2MjcgMC4yMDQtMC4xNDg5IDAuMjc2LTAuMjU4N3MwLjEyNC0wLjIzNzUgMC4xNTgtMC4zODMxYzAuMDM2LTAuMTQ1NiAwLjA1NC0wLjMwMDIgMC4wNTQtMC40NjM4di0wLjA3NzNjMC0wLjE2MTMtMC4wMTgtMC4zMTM2LTAuMDU0LTAuNDU3LTAuMDM0LTAuMTQ1Ni0wLjA4OC0wLjI3NDQtMC4xNjItMC4zODY1LTAuMDcxLTAuMTEyLTAuMTYzLTAuMTk5My0wLjI3NS0wLjI2MjEtMC4xMS0wLjA2NDktMC4yNDEtMC4wOTc0LTAuMzkzLTAuMDk3NC0wLjE1MyAwLTAuMjg1IDAuMDMyNS0wLjM5NyAwLjA5NzQtMC4xMSAwLjA2MjgtMC4yIDAuMTUwMS0wLjI3MiAwLjI2MjEtMC4wNzIgMC4xMTIxLTAuMTI0IDAuMjQwOS0wLjE1OCAwLjM4NjUtMC4wMzQgMC4xNDM0LTAuMDUgMC4yOTU3LTAuMDUgMC40NTd6IiBmaWxsLW9wYWNpdHk9Ii4zOCIvPgogICA8cGF0aCBkPSJtNDguMTk2IDgwLjQ2OXYyLjc5NTloLTE0LjIxM3YtMi40MDI3bDYuOTAyNS03LjUyODdjMC43NTcyLTAuODU0MyAxLjM1NDMtMS41OTIyIDEuNzkxMS0yLjIxMzUgMC40MzY5LTAuNjIxMyAwLjc0MjctMS4xNzk1IDAuOTE3NS0xLjY3NDYgMC4xODQ0LTAuNTA0OSAwLjI3NjYtMC45OTUxIDAuMjc2Ni0xLjQ3MDggMC0wLjY2OTktMC4xMjYyLTEuMjU3Mi0wLjM3ODYtMS43NjIxLTAuMjQyNy0wLjUxNDUtMC42MDE5LTAuOTE3NC0xLjA3NzYtMS4yMDg2LTAuNDc1Ny0wLjMwMS0xLjA1MzMtMC40NTE1LTEuNzMyOS0wLjQ1MTUtMC43ODY0IDAtMS40NDY1IDAuMTY5OS0xLjk4MDUgMC41MDk3LTAuNTMzOSAwLjMzOTgtMC45MzY4IDAuODEwNi0xLjIwODYgMS40MTI2LTAuMjcxOSAwLjU5MjEtMC40MDc4IDEuMjcxNy0wLjQwNzggMi4wMzg3aC0zLjUwOTVjMC0xLjIzMyAwLjI4MTYtMi4zNTkxIDAuODQ0Ni0zLjM3ODUgMC41NjMxLTEuMDI5IDEuMzc4Ni0xLjg0NDUgMi40NDY1LTIuNDQ2NCAxLjA2NzktMC42MTE3IDIuMzU0Mi0wLjkxNzUgMy44NTktMC45MTc1IDEuNDE3NCAwIDIuNjIxMiAwLjIzNzkgMy42MTE0IDAuNzEzNiAwLjk5MDMgMC40NzU3IDEuNzQyNyAxLjE1MDQgMi4yNTcyIDIuMDI0MSAwLjUyNDIgMC44NzM4IDAuNzg2NCAxLjkwNzcgMC43ODY0IDMuMTAxOCAwIDAuNjYwMi0wLjEwNjggMS4zMTU1LTAuMzIwNCAxLjk2NTktMC4yMTM2IDAuNjUwNS0wLjUxOTQgMS4zMDA5LTAuOTE3NCAxLjk1MTQtMC4zODg0IDAuNjQwNy0wLjg0OTUgMS4yODYzLTEuMzgzNSAxLjkzNjctMC41MzM5IDAuNjQwOC0xLjEyMTIgMS4yOTEyLTEuNzYyIDEuOTUxNGwtNC41ODcxIDUuMDUzMWg5Ljc4NTh6bTE2LjQyOSAwdjIuNzk1OWgtMTQuMjEzdi0yLjQwMjdsNi45MDI2LTcuNTI4N2MwLjc1NzItMC44NTQzIDEuMzU0Mi0xLjU5MjIgMS43OTExLTIuMjEzNXMwLjc0MjctMS4xNzk1IDAuOTE3NC0xLjY3NDZjMC4xODQ1LTAuNTA0OSAwLjI3NjctMC45OTUxIDAuMjc2Ny0xLjQ3MDggMC0wLjY2OTktMC4xMjYyLTEuMjU3Mi0wLjM3ODYtMS43NjIxLTAuMjQyNy0wLjUxNDUtMC42MDE5LTAuOTE3NC0xLjA3NzYtMS4yMDg2LTAuNDc1Ny0wLjMwMS0xLjA1MzMtMC40NTE1LTEuNzMyOS0wLjQ1MTUtMC43ODY0IDAtMS40NDY1IDAuMTY5OS0xLjk4MDUgMC41MDk3LTAuNTMzOSAwLjMzOTgtMC45MzY4IDAuODEwNi0xLjIwODcgMS40MTI2LTAuMjcxOCAwLjU5MjEtMC40MDc3IDEuMjcxNy0wLjQwNzcgMi4wMzg3aC0zLjUwOTVjMC0xLjIzMyAwLjI4MTUtMi4zNTkxIDAuODQ0Ni0zLjM3ODUgMC41NjMxLTEuMDI5IDEuMzc4Ni0xLjg0NDUgMi40NDY1LTIuNDQ2NCAxLjA2NzktMC42MTE3IDIuMzU0Mi0wLjkxNzUgMy44NTktMC45MTc1IDEuNDE3NCAwIDIuNjIxMiAwLjIzNzkgMy42MTE0IDAuNzEzNnMxLjc0MjYgMS4xNTA0IDIuMjU3MiAyLjAyNDFjMC41MjQyIDAuODczOCAwLjc4NjMgMS45MDc3IDAuNzg2MyAzLjEwMTggMCAwLjY2MDItMC4xMDY4IDEuMzE1NS0wLjMyMDMgMS45NjU5LTAuMjEzNiAwLjY1MDUtMC41MTk0IDEuMzAwOS0wLjkxNzUgMS45NTE0LTAuMzg4MyAwLjY0MDctMC44NDk0IDEuMjg2My0xLjM4MzQgMS45MzY3LTAuNTMzOSAwLjY0MDgtMS4xMjEzIDEuMjkxMi0xLjc2MiAxLjk1MTRsLTQuNTg3MSA1LjA1MzFoOS43ODU4em0yLjQ5MjUtMTQuODFjMC0wLjcwODcgMC4xNzQ3LTEuMzU5MiAwLjUyNDItMS45NTE0czAuODE1NS0xLjA2MyAxLjM5OC0xLjQxMjVjMC41OTIyLTAuMzU5MiAxLjIzMjktMC41Mzg4IDEuOTIyMi0wLjUzODggMC42OTkgMCAxLjMzNDkgMC4xNzk2IDEuOTA3NyAwLjUzODggMC41NzI4IDAuMzQ5NSAxLjAyOTEgMC44MjAzIDEuMzY4OCAxLjQxMjUgMC4zNDk1IDAuNTkyMiAwLjUyNDMgMS4yNDI3IDAuNTI0MyAxLjk1MTRzLTAuMTc0OCAxLjM1OTEtMC41MjQzIDEuOTUxM2MtMC4zMzk3IDAuNTgyNS0wLjc5NiAxLjA0MzYtMS4zNjg4IDEuMzgzNHMtMS4yMDg3IDAuNTA5Ny0xLjkwNzcgMC41MDk3Yy0wLjY4OTMgMC0xLjMzLTAuMTY5OS0xLjkyMjItMC41MDk3LTAuNTgyNS0wLjMzOTgtMS4wNDg1LTAuODAwOS0xLjM5OC0xLjM4MzQtMC4zNDk1LTAuNTkyMi0wLjUyNDItMS4yNDI2LTAuNTI0Mi0xLjk1MTN6bTEuOTY1OSAwYzAgMC41MjQyIDAuMTg0NSAwLjk2NTkgMC41NTM0IDEuMzI1MSAwLjM2ODkgMC4zNDk1IDAuODEwNiAwLjUyNDMgMS4zMjUxIDAuNTI0MyAwLjUxNDYgMCAwLjk0NjYtMC4xNzQ4IDEuMjk2MS0wLjUyNDNzMC41MjQyLTAuNzkxMiAwLjUyNDItMS4zMjUxYzAtMC41NDM3LTAuMTc0Ny0wLjk5NTEtMC41MjQyLTEuMzU0M3MtMC43ODE1LTAuNTM4OC0xLjI5NjEtMC41Mzg4Yy0wLjUxNDUgMC0wLjk1NjIgMC4xNzk2LTEuMzI1MSAwLjUzODhzLTAuNTUzNCAwLjgxMDYtMC41NTM0IDEuMzU0M3ptMjEuNzI5IDEwLjcwM2gzLjY0MDZjLTAuMTE2NSAxLjM4ODMtMC41MDQ4IDIuNjI2MS0xLjE2NSAzLjcxMzQtMC42NjAxIDEuMDc3Ni0xLjU4NzMgMS45MjcxLTIuNzgxNCAyLjU0ODRzLTIuNjQ1NCAwLjkzMi00LjM1NDEgMC45MzJjLTEuMzEwNiAwLTIuNDkwMS0wLjIzMy0zLjUzODYtMC42OTktMS4wNDg1LTAuNDc1Ny0xLjk0NjUtMS4xNDU2LTIuNjk0LTIuMDA5Ni0wLjc0NzYtMC44NzM3LTEuMzIwNC0xLjkyNzEtMS43MTg0LTMuMTYtMC4zODgzLTEuMjMyOS0wLjU4MjUtMi42MTE1LTAuNTgyNS00LjEzNTd2LTEuNzYyYzAtMS41MjQyIDAuMTk5LTIuOTAyOCAwLjU5NzEtNC4xMzU3IDAuNDA3Ny0xLjIzMjkgMC45OTAyLTIuMjg2MyAxLjc0NzQtMy4xNiAwLjc1NzMtMC44ODM1IDEuNjY1LTEuNTU4MiAyLjcyMzItMi4wMjQyIDEuMDY3OS0wLjQ2NiAyLjI2NjktMC42OTkgMy41OTY5LTAuNjk5IDEuNjg5MiAwIDMuMTE2MyAwLjMxMDcgNC4yODEzIDAuOTMyczIuMDY3OCAxLjQ4MDUgMi43MDg2IDIuNTc3NWMwLjY1MDQgMS4wOTcxIDEuMDQ4NCAyLjM1NDMgMS4xOTQxIDMuNzcxN2gtMy42NDA2Yy0wLjA5NzEtMC45MTI2LTAuMzEwNy0xLjY5NDEtMC42NDA3LTIuMzQ0Ni0wLjMyMDQtMC42NTA0LTAuNzk2MS0xLjE0NTUtMS40MjcxLTEuNDg1My0wLjYzMTEtMC4zNDk1LTEuNDU2My0wLjUyNDItMi40NzU2LTAuNTI0Mi0wLjgzNDkgMC0xLjU2MyAwLjE1NTMtMi4xODQ0IDAuNDY1OS0wLjYyMTMgMC4zMTA3LTEuMTQwNyAwLjc2Ny0xLjU1ODEgMS4zNjg5LTAuNDE3NSAwLjYwMTktMC43MzMgMS4zNDQ2LTAuOTQ2NiAyLjIyOC0wLjIwMzkgMC44NzM4LTAuMzA1OCAxLjg3MzctMC4zMDU4IDIuOTk5OXYxLjc5MTFjMCAxLjA2NzkgMC4wOTIyIDIuMDM4NyAwLjI3NjcgMi45MTI1IDAuMTk0MiAwLjg2NCAwLjQ4NTQgMS42MDY3IDAuODczNyAyLjIyOCAwLjM5ODEgMC42MjEzIDAuOTAyOSAxLjEwMTkgMS41MTQ1IDEuNDQxNyAwLjYxMTYgMC4zMzk3IDEuMzQ0NiAwLjUwOTYgMi4xOTg5IDAuNTA5NiAxLjAzODggMCAxLjg3ODUtMC4xNjUgMi41MTkzLTAuNDk1MSAwLjY1MDQtMC4zMzAxIDEuMTQwNy0wLjgxMDYgMS40NzA4LTEuNDQxNiAwLjMzOTgtMC42NDA4IDAuNTYzLTEuNDIyMyAwLjY2OTgtMi4zNDQ2eiIgZmlsbC1vcGFjaXR5PSIuODciLz4KICA8L2c+CiA8L2c+CiA8ZGVmcz4KICA8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfMTE0Ml8yMDM5NTQiIHg9Ii45MTE3NiIgeT0iLjIwNTg4IiB3aWR0aD0iMTI2LjE4IiBoZWlnaHQ9IjEyNi4xOCIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICA8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgogICA8ZmVDb2xvck1hdHJpeCBpbj0iU291cmNlQWxwaGEiIHJlc3VsdD0iaGFyZEFscGhhIiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIi8+CiAgIDxmZU9mZnNldCBkeT0iMi4yOTQxMiIvPgogICA8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIyLjI5NDEyIi8+CiAgIDxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgogICA8ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDQgMCIvPgogICA8ZmVCbGVuZCBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfMTE0Ml8yMDM5NTQiLz4KICAgPGZlQmxlbmQgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9kcm9wU2hhZG93XzExNDJfMjAzOTU0IiByZXN1bHQ9InNoYXBlIi8+CiAgPC9maWx0ZXI+CiA8L2RlZnM+Cjwvc3ZnPgo=", "description": "Designed to display single value of the selected attribute or timeseries data. Widget styles are customizable.", "descriptor": { "type": "latest", - "sizeX": 2.5, - "sizeY": 2.5, + "sizeX": 3, + "sizeY": 3, "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n previewWidth: '250px',\n previewHeight: '250px'\n };\n};\n\nself.onDestroy = function() {\n};\n", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n previewWidth: '250px',\n previewHeight: '250px',\n absoluteHeader: true\n };\n};\n\nself.onDestroy = function() {\n};\n", "settingsSchema": "", "dataKeySettingsSchema": "", - "settingsDirective": "", + "settingsDirective": "tb-value-card-widget-settings", "hasBasicMode": true, "basicModeDirective": "tb-value-card-basic-config", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}" @@ -250,7 +250,7 @@ { "alias": "horizontal_value_card", "name": "Horizontal value card", - "image": null, + "image": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzk5IiBoZWlnaHQ9IjEwOCIgdmlld0JveD0iMCAwIDM5OSAxMDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2RfMTI0Nl80NDQ0NykiPgo8cmVjdCB4PSI4IiB5PSI0IiB3aWR0aD0iMzgzIiBoZWlnaHQ9IjkyIiByeD0iNCIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTU3LjAwMDEgNTEuNjY2N1YzOC4zMzM0QzU3LjAwMDEgMzUuNTY2NyA1NC43NjY3IDMzLjMzMzQgNTIuMDAwMSAzMy4zMzM0QzQ5LjIzMzQgMzMuMzMzNCA0Ny4wMDAxIDM1LjU2NjcgNDcuMDAwMSAzOC4zMzM0VjUxLjY2NjdDNDQuOTgzNCA1My4xODM0IDQzLjY2NjcgNTUuNjE2NyA0My42NjY3IDU4LjMzMzRDNDMuNjY2NyA2Mi45MzM0IDQ3LjQwMDEgNjYuNjY2NyA1Mi4wMDAxIDY2LjY2NjdDNTYuNjAwMSA2Ni42NjY3IDYwLjMzMzQgNjIuOTMzNCA2MC4zMzM0IDU4LjMzMzRDNjAuMzMzNCA1NS42MTY3IDU5LjAxNjcgNTMuMTgzNCA1Ny4wMDAxIDUxLjY2NjdaTTUwLjMzMzQgMzguMzMzNEM1MC4zMzM0IDM3LjQxNjcgNTEuMDgzNCAzNi42NjY3IDUyLjAwMDEgMzYuNjY2N0M1Mi45MTY3IDM2LjY2NjcgNTMuNjY2NyAzNy40MTY3IDUzLjY2NjcgMzguMzMzNEg1Mi4wMDAxVjQwSDUzLjY2NjdWNDMuMzMzNEg1Mi4wMDAxVjQ1SDUzLjY2NjdWNDguMzMzNEg1MC4zMzM0VjM4LjMzMzRaIiBmaWxsPSIjNTQ2OUZGIi8+CjxwYXRoIGQ9Ik04NS44MzU5IDM1LjYyNVY0N0g4My44OTA2VjM1LjYyNUg4NS44MzU5Wk04OS40MDYyIDM1LjYyNVYzNy4xODc1SDgwLjM1MTZWMzUuNjI1SDg5LjQwNjJaTTkzLjk0NTMgNDcuMTU2MkM5My4zMjAzIDQ3LjE1NjIgOTIuNzU1MiA0Ny4wNTQ3IDkyLjI1IDQ2Ljg1MTZDOTEuNzUgNDYuNjQzMiA5MS4zMjI5IDQ2LjM1NDIgOTAuOTY4OCA0NS45ODQ0QzkwLjYxOTggNDUuNjE0NiA5MC4zNTE2IDQ1LjE3OTcgOTAuMTY0MSA0NC42Nzk3Qzg5Ljk3NjYgNDQuMTc5NyA4OS44ODI4IDQzLjY0MDYgODkuODgyOCA0My4wNjI1VjQyLjc1Qzg5Ljg4MjggNDIuMDg4NSA4OS45NzkyIDQxLjQ4OTYgOTAuMTcxOSA0MC45NTMxQzkwLjM2NDYgNDAuNDE2NyA5MC42MzI4IDM5Ljk1ODMgOTAuOTc2NiAzOS41NzgxQzkxLjMyMDMgMzkuMTkyNyA5MS43MjY2IDM4Ljg5ODQgOTIuMTk1MyAzOC42OTUzQzkyLjY2NDEgMzguNDkyMiA5My4xNzE5IDM4LjM5MDYgOTMuNzE4OCAzOC4zOTA2Qzk0LjMyMjkgMzguMzkwNiA5NC44NTE2IDM4LjQ5MjIgOTUuMzA0NyAzOC42OTUzQzk1Ljc1NzggMzguODk4NCA5Ni4xMzI4IDM5LjE4NDkgOTYuNDI5NyAzOS41NTQ3Qzk2LjczMTggMzkuOTE5MyA5Ni45NTU3IDQwLjM1NDIgOTcuMTAxNiA0MC44NTk0Qzk3LjI1MjYgNDEuMzY0NiA5Ny4zMjgxIDQxLjkyMTkgOTcuMzI4MSA0Mi41MzEyVjQzLjMzNTlIOTAuNzk2OVY0MS45ODQ0SDk1LjQ2ODhWNDEuODM1OUM5NS40NTgzIDQxLjQ5NzQgOTUuMzkwNiA0MS4xNzk3IDk1LjI2NTYgNDAuODgyOEM5NS4xNDU4IDQwLjU4NTkgOTQuOTYwOSA0MC4zNDY0IDk0LjcxMDkgNDAuMTY0MUM5NC40NjA5IDM5Ljk4MTggOTQuMTI3NiAzOS44OTA2IDkzLjcxMDkgMzkuODkwNkM5My4zOTg0IDM5Ljg5MDYgOTMuMTE5OCAzOS45NTgzIDkyLjg3NSA0MC4wOTM4QzkyLjYzNTQgNDAuMjI0IDkyLjQzNDkgNDAuNDE0MSA5Mi4yNzM0IDQwLjY2NDFDOTIuMTEyIDQwLjkxNDEgOTEuOTg3IDQxLjIxNjEgOTEuODk4NCA0MS41NzAzQzkxLjgxNTEgNDEuOTE5MyA5MS43NzM0IDQyLjMxMjUgOTEuNzczNCA0Mi43NVY0My4wNjI1QzkxLjc3MzQgNDMuNDMyMyA5MS44MjI5IDQzLjc3NiA5MS45MjE5IDQ0LjA5MzhDOTIuMDI2IDQ0LjQwNjIgOTIuMTc3MSA0NC42Nzk3IDkyLjM3NSA0NC45MTQxQzkyLjU3MjkgNDUuMTQ4NCA5Mi44MTI1IDQ1LjMzMzMgOTMuMDkzOCA0NS40Njg4QzkzLjM3NSA0NS41OTkgOTMuNjk1MyA0NS42NjQxIDk0LjA1NDcgNDUuNjY0MUM5NC41MDc4IDQ1LjY2NDEgOTQuOTExNSA0NS41NzI5IDk1LjI2NTYgNDUuMzkwNkM5NS42MTk4IDQ1LjIwODMgOTUuOTI3MSA0NC45NTA1IDk2LjE4NzUgNDQuNjE3Mkw5Ny4xNzk3IDQ1LjU3ODFDOTYuOTk3NCA0NS44NDM4IDk2Ljc2MDQgNDYuMDk5IDk2LjQ2ODggNDYuMzQzOEM5Ni4xNzcxIDQ2LjU4MzMgOTUuODIwMyA0Ni43Nzg2IDk1LjM5ODQgNDYuOTI5N0M5NC45ODE4IDQ3LjA4MDcgOTQuNDk3NCA0Ny4xNTYyIDkzLjk0NTMgNDcuMTU2MlpNMTAwLjkzIDQwLjI2NTZWNDdIOTkuMDQ2OVYzOC41NDY5SDEwMC44MkwxMDAuOTMgNDAuMjY1NlpNMTAwLjYyNSA0Mi40NjA5TDk5Ljk4NDQgNDIuNDUzMUM5OS45ODQ0IDQxLjg2OTggMTAwLjA1NyA0MS4zMzA3IDEwMC4yMDMgNDAuODM1OUMxMDAuMzQ5IDQwLjM0MTEgMTAwLjU2MiAzOS45MTE1IDEwMC44NDQgMzkuNTQ2OUMxMDEuMTI1IDM5LjE3NzEgMTAxLjQ3NCAzOC44OTMyIDEwMS44OTEgMzguNjk1M0MxMDIuMzEyIDM4LjQ5MjIgMTAyLjc5OSAzOC4zOTA2IDEwMy4zNTIgMzguMzkwNkMxMDMuNzM3IDM4LjM5MDYgMTA0LjA4OSAzOC40NDc5IDEwNC40MDYgMzguNTYyNUMxMDQuNzI5IDM4LjY3MTkgMTA1LjAwOCAzOC44NDY0IDEwNS4yNDIgMzkuMDg1OUMxMDUuNDgyIDM5LjMyNTUgMTA1LjY2NCAzOS42MzI4IDEwNS43ODkgNDAuMDA3OEMxMDUuOTE5IDQwLjM4MjggMTA1Ljk4NCA0MC44MzU5IDEwNS45ODQgNDEuMzY3MlY0N0gxMDQuMTAyVjQxLjUzMTJDMTA0LjEwMiA0MS4xMTk4IDEwNC4wMzkgNDAuNzk2OSAxMDMuOTE0IDQwLjU2MjVDMTAzLjc5NCA0MC4zMjgxIDEwMy42MiA0MC4xNjE1IDEwMy4zOTEgNDAuMDYyNUMxMDMuMTY3IDM5Ljk1ODMgMTAyLjg5OCAzOS45MDYyIDEwMi41ODYgMzkuOTA2MkMxMDIuMjMyIDM5LjkwNjIgMTAxLjkzIDM5Ljk3NCAxMDEuNjggNDAuMTA5NEMxMDEuNDM1IDQwLjI0NDggMTAxLjIzNCA0MC40Mjk3IDEwMS4wNzggNDAuNjY0MUMxMDAuOTIyIDQwLjg5ODQgMTAwLjgwNyA0MS4xNjkzIDEwMC43MzQgNDEuNDc2NkMxMDAuNjYxIDQxLjc4MzkgMTAwLjYyNSA0Mi4xMTIgMTAwLjYyNSA0Mi40NjA5Wk0xMDUuODY3IDQxLjk2MDlMMTA0Ljk4NCA0Mi4xNTYyQzEwNC45ODQgNDEuNjQ1OCAxMDUuMDU1IDQxLjE2NDEgMTA1LjE5NSA0MC43MTA5QzEwNS4zNDEgNDAuMjUyNiAxMDUuNTUyIDM5Ljg1MTYgMTA1LjgyOCAzOS41MDc4QzEwNi4xMDkgMzkuMTU4OSAxMDYuNDU2IDM4Ljg4NTQgMTA2Ljg2NyAzOC42ODc1QzEwNy4yNzkgMzguNDg5NiAxMDcuNzUgMzguMzkwNiAxMDguMjgxIDM4LjM5MDZDMTA4LjcxNCAzOC4zOTA2IDEwOS4wOTkgMzguNDUwNSAxMDkuNDM4IDM4LjU3MDNDMTA5Ljc4MSAzOC42ODQ5IDExMC4wNzMgMzguODY3MiAxMTAuMzEyIDM5LjExNzJDMTEwLjU1MiAzOS4zNjcyIDExMC43MzQgMzkuNjkyNyAxMTAuODU5IDQwLjA5MzhDMTEwLjk4NCA0MC40ODk2IDExMS4wNDcgNDAuOTY4OCAxMTEuMDQ3IDQxLjUzMTJWNDdIMTA5LjE1NlY0MS41MjM0QzEwOS4xNTYgNDEuMDk2NCAxMDkuMDk0IDQwLjc2NTYgMTA4Ljk2OSA0MC41MzEyQzEwOC44NDkgNDAuMjk2OSAxMDguNjc3IDQwLjEzNTQgMTA4LjQ1MyA0MC4wNDY5QzEwOC4yMjkgMzkuOTUzMSAxMDcuOTYxIDM5LjkwNjIgMTA3LjY0OCAzOS45MDYyQzEwNy4zNTcgMzkuOTA2MiAxMDcuMDk5IDM5Ljk2MDkgMTA2Ljg3NSA0MC4wNzAzQzEwNi42NTYgNDAuMTc0NSAxMDYuNDcxIDQwLjMyMjkgMTA2LjMyIDQwLjUxNTZDMTA2LjE2OSA0MC43MDMxIDEwNi4wNTUgNDAuOTE5MyAxMDUuOTc3IDQxLjE2NDFDMTA1LjkwNCA0MS40MDg5IDEwNS44NjcgNDEuNjc0NSAxMDUuODY3IDQxLjk2MDlaTTExNS4xMjUgNDAuMTcxOVY1MC4yNUgxMTMuMjQyVjM4LjU0NjlIMTE0Ljk3N0wxMTUuMTI1IDQwLjE3MTlaTTEyMC42MzMgNDIuNjk1M1Y0Mi44NTk0QzEyMC42MzMgNDMuNDc0IDEyMC41NiA0NC4wNDQzIDEyMC40MTQgNDQuNTcwM0MxMjAuMjczIDQ1LjA5MTEgMTIwLjA2MiA0NS41NDY5IDExOS43ODEgNDUuOTM3NUMxMTkuNTA1IDQ2LjMyMjkgMTE5LjE2NCA0Ni42MjI0IDExOC43NTggNDYuODM1OUMxMTguMzUyIDQ3LjA0OTUgMTE3Ljg4MyA0Ny4xNTYyIDExNy4zNTIgNDcuMTU2MkMxMTYuODI2IDQ3LjE1NjIgMTE2LjM2NSA0Ny4wNTk5IDExNS45NjkgNDYuODY3MkMxMTUuNTc4IDQ2LjY2OTMgMTE1LjI0NyA0Ni4zOTA2IDExNC45NzcgNDYuMDMxMkMxMTQuNzA2IDQ1LjY3MTkgMTE0LjQ4NyA0NS4yNSAxMTQuMzIgNDQuNzY1NkMxMTQuMTU5IDQ0LjI3NiAxMTQuMDQ0IDQzLjczOTYgMTEzLjk3NyA0My4xNTYyVjQyLjUyMzRDMTE0LjA0NCA0MS45MDM2IDExNC4xNTkgNDEuMzQxMSAxMTQuMzIgNDAuODM1OUMxMTQuNDg3IDQwLjMzMDcgMTE0LjcwNiAzOS44OTU4IDExNC45NzcgMzkuNTMxMkMxMTUuMjQ3IDM5LjE2NjcgMTE1LjU3OCAzOC44ODU0IDExNS45NjkgMzguNjg3NUMxMTYuMzU5IDM4LjQ4OTYgMTE2LjgxNSAzOC4zOTA2IDExNy4zMzYgMzguMzkwNkMxMTcuODY3IDM4LjM5MDYgMTE4LjMzOSAzOC40OTQ4IDExOC43NSAzOC43MDMxQzExOS4xNjEgMzguOTA2MiAxMTkuNTA4IDM5LjE5NzkgMTE5Ljc4OSAzOS41NzgxQzEyMC4wNyAzOS45NTMxIDEyMC4yODEgNDAuNDA2MiAxMjAuNDIyIDQwLjkzNzVDMTIwLjU2MiA0MS40NjM1IDEyMC42MzMgNDIuMDQ5NSAxMjAuNjMzIDQyLjY5NTNaTTExOC43NSA0Mi44NTk0VjQyLjY5NTNDMTE4Ljc1IDQyLjMwNDcgMTE4LjcxNCA0MS45NDI3IDExOC42NDEgNDEuNjA5NEMxMTguNTY4IDQxLjI3MDggMTE4LjQ1MyA0MC45NzQgMTE4LjI5NyA0MC43MTg4QzExOC4xNDEgNDAuNDYzNSAxMTcuOTQgNDAuMjY1NiAxMTcuNjk1IDQwLjEyNUMxMTcuNDU2IDM5Ljk3OTIgMTE3LjE2NyAzOS45MDYyIDExNi44MjggMzkuOTA2MkMxMTYuNDk1IDM5LjkwNjIgMTE2LjIwOCAzOS45NjM1IDExNS45NjkgNDAuMDc4MUMxMTUuNzI5IDQwLjE4NzUgMTE1LjUyOSA0MC4zNDExIDExNS4zNjcgNDAuNTM5MUMxMTUuMjA2IDQwLjczNyAxMTUuMDgxIDQwLjk2ODggMTE0Ljk5MiA0MS4yMzQ0QzExNC45MDQgNDEuNDk0OCAxMTQuODQxIDQxLjc3ODYgMTE0LjgwNSA0Mi4wODU5VjQzLjYwMTZDMTE0Ljg2NyA0My45NzY2IDExNC45NzQgNDQuMzIwMyAxMTUuMTI1IDQ0LjYzMjhDMTE1LjI3NiA0NC45NDUzIDExNS40OSA0NS4xOTUzIDExNS43NjYgNDUuMzgyOEMxMTYuMDQ3IDQ1LjU2NTEgMTE2LjQwNiA0NS42NTYyIDExNi44NDQgNDUuNjU2MkMxMTcuMTgyIDQ1LjY1NjIgMTE3LjQ3MSA0NS41ODMzIDExNy43MTEgNDUuNDM3NUMxMTcuOTUxIDQ1LjI5MTcgMTE4LjE0NiA0NS4wOTExIDExOC4yOTcgNDQuODM1OUMxMTguNDUzIDQ0LjU3NTUgMTE4LjU2OCA0NC4yNzYgMTE4LjY0MSA0My45Mzc1QzExOC43MTQgNDMuNTk5IDExOC43NSA0My4yMzk2IDExOC43NSA0Mi44NTk0Wk0xMjYuMjExIDQ3LjE1NjJDMTI1LjU4NiA0Ny4xNTYyIDEyNS4wMjEgNDcuMDU0NyAxMjQuNTE2IDQ2Ljg1MTZDMTI0LjAxNiA0Ni42NDMyIDEyMy41ODkgNDYuMzU0MiAxMjMuMjM0IDQ1Ljk4NDRDMTIyLjg4NSA0NS42MTQ2IDEyMi42MTcgNDUuMTc5NyAxMjIuNDMgNDQuNjc5N0MxMjIuMjQyIDQ0LjE3OTcgMTIyLjE0OCA0My42NDA2IDEyMi4xNDggNDMuMDYyNVY0Mi43NUMxMjIuMTQ4IDQyLjA4ODUgMTIyLjI0NSA0MS40ODk2IDEyMi40MzggNDAuOTUzMUMxMjIuNjMgNDAuNDE2NyAxMjIuODk4IDM5Ljk1ODMgMTIzLjI0MiAzOS41NzgxQzEyMy41ODYgMzkuMTkyNyAxMjMuOTkyIDM4Ljg5ODQgMTI0LjQ2MSAzOC42OTUzQzEyNC45MyAzOC40OTIyIDEyNS40MzggMzguMzkwNiAxMjUuOTg0IDM4LjM5MDZDMTI2LjU4OSAzOC4zOTA2IDEyNy4xMTcgMzguNDkyMiAxMjcuNTcgMzguNjk1M0MxMjguMDIzIDM4Ljg5ODQgMTI4LjM5OCAzOS4xODQ5IDEyOC42OTUgMzkuNTU0N0MxMjguOTk3IDM5LjkxOTMgMTI5LjIyMSA0MC4zNTQyIDEyOS4zNjcgNDAuODU5NEMxMjkuNTE4IDQxLjM2NDYgMTI5LjU5NCA0MS45MjE5IDEyOS41OTQgNDIuNTMxMlY0My4zMzU5SDEyMy4wNjJWNDEuOTg0NEgxMjcuNzM0VjQxLjgzNTlDMTI3LjcyNCA0MS40OTc0IDEyNy42NTYgNDEuMTc5NyAxMjcuNTMxIDQwLjg4MjhDMTI3LjQxMSA0MC41ODU5IDEyNy4yMjcgNDAuMzQ2NCAxMjYuOTc3IDQwLjE2NDFDMTI2LjcyNyAzOS45ODE4IDEyNi4zOTMgMzkuODkwNiAxMjUuOTc3IDM5Ljg5MDZDMTI1LjY2NCAzOS44OTA2IDEyNS4zODUgMzkuOTU4MyAxMjUuMTQxIDQwLjA5MzhDMTI0LjkwMSA0MC4yMjQgMTI0LjcwMSA0MC40MTQxIDEyNC41MzkgNDAuNjY0MUMxMjQuMzc4IDQwLjkxNDEgMTI0LjI1MyA0MS4yMTYxIDEyNC4xNjQgNDEuNTcwM0MxMjQuMDgxIDQxLjkxOTMgMTI0LjAzOSA0Mi4zMTI1IDEyNC4wMzkgNDIuNzVWNDMuMDYyNUMxMjQuMDM5IDQzLjQzMjMgMTI0LjA4OSA0My43NzYgMTI0LjE4OCA0NC4wOTM4QzEyNC4yOTIgNDQuNDA2MiAxMjQuNDQzIDQ0LjY3OTcgMTI0LjY0MSA0NC45MTQxQzEyNC44MzkgNDUuMTQ4NCAxMjUuMDc4IDQ1LjMzMzMgMTI1LjM1OSA0NS40Njg4QzEyNS42NDEgNDUuNTk5IDEyNS45NjEgNDUuNjY0MSAxMjYuMzIgNDUuNjY0MUMxMjYuNzczIDQ1LjY2NDEgMTI3LjE3NyA0NS41NzI5IDEyNy41MzEgNDUuMzkwNkMxMjcuODg1IDQ1LjIwODMgMTI4LjE5MyA0NC45NTA1IDEyOC40NTMgNDQuNjE3MkwxMjkuNDQ1IDQ1LjU3ODFDMTI5LjI2MyA0NS44NDM4IDEyOS4wMjYgNDYuMDk5IDEyOC43MzQgNDYuMzQzOEMxMjguNDQzIDQ2LjU4MzMgMTI4LjA4NiA0Ni43Nzg2IDEyNy42NjQgNDYuOTI5N0MxMjcuMjQ3IDQ3LjA4MDcgMTI2Ljc2MyA0Ny4xNTYyIDEyNi4yMTEgNDcuMTU2MlpNMTMzLjIwMyA0MC4xNTYyVjQ3SDEzMS4zMlYzOC41NDY5SDEzMy4xMTdMMTMzLjIwMyA0MC4xNTYyWk0xMzUuNzg5IDM4LjQ5MjJMMTM1Ljc3MyA0MC4yNDIyQzEzNS42NTkgNDAuMjIxNCAxMzUuNTM0IDQwLjIwNTcgMTM1LjM5OCA0MC4xOTUzQzEzNS4yNjggNDAuMTg0OSAxMzUuMTM4IDQwLjE3OTcgMTM1LjAwOCA0MC4xNzk3QzEzNC42ODUgNDAuMTc5NyAxMzQuNDAxIDQwLjIyNjYgMTM0LjE1NiA0MC4zMjAzQzEzMy45MTEgNDAuNDA4OSAxMzMuNzA2IDQwLjUzOTEgMTMzLjUzOSA0MC43MTA5QzEzMy4zNzggNDAuODc3NiAxMzMuMjUzIDQxLjA4MDcgMTMzLjE2NCA0MS4zMjAzQzEzMy4wNzYgNDEuNTU5OSAxMzMuMDIzIDQxLjgyODEgMTMzLjAwOCA0Mi4xMjVMMTMyLjU3OCA0Mi4xNTYyQzEzMi41NzggNDEuNjI1IDEzMi42MyA0MS4xMzI4IDEzMi43MzQgNDAuNjc5N0MxMzIuODM5IDQwLjIyNjYgMTMyLjk5NSAzOS44MjgxIDEzMy4yMDMgMzkuNDg0NEMxMzMuNDE3IDM5LjE0MDYgMTMzLjY4MiAzOC44NzI0IDEzNCAzOC42Nzk3QzEzNC4zMjMgMzguNDg3IDEzNC42OTUgMzguMzkwNiAxMzUuMTE3IDM4LjM5MDZDMTM1LjIzMiAzOC4zOTA2IDEzNS4zNTQgMzguNDAxIDEzNS40ODQgMzguNDIxOUMxMzUuNjIgMzguNDQyNyAxMzUuNzIxIDM4LjQ2NjEgMTM1Ljc4OSAzOC40OTIyWk0xNDEuNzAzIDQ1LjMwNDdWNDEuMjczNEMxNDEuNzAzIDQwLjk3MTQgMTQxLjY0OCA0MC43MTA5IDE0MS41MzkgNDAuNDkyMkMxNDEuNDMgNDAuMjczNCAxNDEuMjYzIDQwLjEwNDIgMTQxLjAzOSAzOS45ODQ0QzE0MC44MiAzOS44NjQ2IDE0MC41NDQgMzkuODA0NyAxNDAuMjExIDM5LjgwNDdDMTM5LjkwNCAzOS44MDQ3IDEzOS42MzggMzkuODU2OCAxMzkuNDE0IDM5Ljk2MDlDMTM5LjE5IDQwLjA2NTEgMTM5LjAxNiA0MC4yMDU3IDEzOC44OTEgNDAuMzgyOEMxMzguNzY2IDQwLjU1OTkgMTM4LjcwMyA0MC43NjA0IDEzOC43MDMgNDAuOTg0NEgxMzYuODI4QzEzNi44MjggNDAuNjUxIDEzNi45MDkgNDAuMzI4MSAxMzcuMDcgNDAuMDE1NkMxMzcuMjMyIDM5LjcwMzEgMTM3LjQ2NiAzOS40MjQ1IDEzNy43NzMgMzkuMTc5N0MxMzguMDgxIDM4LjkzNDkgMTM4LjQ0OCAzOC43NDIyIDEzOC44NzUgMzguNjAxNkMxMzkuMzAyIDM4LjQ2MDkgMTM5Ljc4MSAzOC4zOTA2IDE0MC4zMTIgMzguMzkwNkMxNDAuOTQ4IDM4LjM5MDYgMTQxLjUxIDM4LjQ5NzQgMTQyIDM4LjcxMDlDMTQyLjQ5NSAzOC45MjQ1IDE0Mi44ODMgMzkuMjQ3NCAxNDMuMTY0IDM5LjY3OTdDMTQzLjQ1MSA0MC4xMDY4IDE0My41OTQgNDAuNjQzMiAxNDMuNTk0IDQxLjI4OTFWNDUuMDQ2OUMxNDMuNTk0IDQ1LjQzMjMgMTQzLjYyIDQ1Ljc3ODYgMTQzLjY3MiA0Ni4wODU5QzE0My43MjkgNDYuMzg4IDE0My44MSA0Ni42NTEgMTQzLjkxNCA0Ni44NzVWNDdIMTQxLjk4NEMxNDEuODk2IDQ2Ljc5NjkgMTQxLjgyNiA0Ni41MzkxIDE0MS43NzMgNDYuMjI2NkMxNDEuNzI3IDQ1LjkwODkgMTQxLjcwMyA0NS42MDE2IDE0MS43MDMgNDUuMzA0N1pNMTQxLjk3NyA0MS44NTk0TDE0MS45OTIgNDMuMDIzNEgxNDAuNjQxQzE0MC4yOTIgNDMuMDIzNCAxMzkuOTg0IDQzLjA1NzMgMTM5LjcxOSA0My4xMjVDMTM5LjQ1MyA0My4xODc1IDEzOS4yMzIgNDMuMjgxMiAxMzkuMDU1IDQzLjQwNjJDMTM4Ljg3OCA0My41MzEyIDEzOC43NDUgNDMuNjgyMyAxMzguNjU2IDQzLjg1OTRDMTM4LjU2OCA0NC4wMzY1IDEzOC41MjMgNDQuMjM3IDEzOC41MjMgNDQuNDYwOUMxMzguNTIzIDQ0LjY4NDkgMTM4LjU3NiA0NC44OTA2IDEzOC42OCA0NS4wNzgxQzEzOC43ODQgNDUuMjYwNCAxMzguOTM1IDQ1LjQwMzYgMTM5LjEzMyA0NS41MDc4QzEzOS4zMzYgNDUuNjEyIDEzOS41ODEgNDUuNjY0MSAxMzkuODY3IDQ1LjY2NDFDMTQwLjI1MyA0NS42NjQxIDE0MC41ODkgNDUuNTg1OSAxNDAuODc1IDQ1LjQyOTdDMTQxLjE2NyA0NS4yNjgyIDE0MS4zOTYgNDUuMDcyOSAxNDEuNTYyIDQ0Ljg0MzhDMTQxLjcyOSA0NC42MDk0IDE0MS44MTggNDQuMzg4IDE0MS44MjggNDQuMTc5N0wxNDIuNDM4IDQ1LjAxNTZDMTQyLjM3NSA0NS4yMjkyIDE0Mi4yNjggNDUuNDU4MyAxNDIuMTE3IDQ1LjcwMzFDMTQxLjk2NiA0NS45NDc5IDE0MS43NjggNDYuMTgyMyAxNDEuNTIzIDQ2LjQwNjJDMTQxLjI4NCA0Ni42MjUgMTQwLjk5NSA0Ni44MDQ3IDE0MC42NTYgNDYuOTQ1M0MxNDAuMzIzIDQ3LjA4NTkgMTM5LjkzOCA0Ny4xNTYyIDEzOS41IDQ3LjE1NjJDMTM4Ljk0OCA0Ny4xNTYyIDEzOC40NTYgNDcuMDQ2OSAxMzguMDIzIDQ2LjgyODFDMTM3LjU5MSA0Ni42MDQyIDEzNy4yNTMgNDYuMzA0NyAxMzcuMDA4IDQ1LjkyOTdDMTM2Ljc2MyA0NS41NDk1IDEzNi42NDEgNDUuMTE5OCAxMzYuNjQxIDQ0LjY0MDZDMTM2LjY0MSA0NC4xOTI3IDEzNi43MjQgNDMuNzk2OSAxMzYuODkxIDQzLjQ1MzFDMTM3LjA2MiA0My4xMDQyIDEzNy4zMTIgNDIuODEyNSAxMzcuNjQxIDQyLjU3ODFDMTM3Ljk3NCA0Mi4zNDM4IDEzOC4zOCA0Mi4xNjY3IDEzOC44NTkgNDIuMDQ2OUMxMzkuMzM5IDQxLjkyMTkgMTM5Ljg4NSA0MS44NTk0IDE0MC41IDQxLjg1OTRIMTQxLjk3N1pNMTQ5LjY4OCAzOC41NDY5VjM5LjkyMTlIMTQ0LjkyMlYzOC41NDY5SDE0OS42ODhaTTE0Ni4yOTcgMzYuNDc2NkgxNDguMThWNDQuNjY0MUMxNDguMTggNDQuOTI0NSAxNDguMjE2IDQ1LjEyNSAxNDguMjg5IDQ1LjI2NTZDMTQ4LjM2NyA0NS40MDEgMTQ4LjQ3NCA0NS40OTIyIDE0OC42MDkgNDUuNTM5MUMxNDguNzQ1IDQ1LjU4NTkgMTQ4LjkwNCA0NS42MDk0IDE0OS4wODYgNDUuNjA5NEMxNDkuMjE2IDQ1LjYwOTQgMTQ5LjM0MSA0NS42MDE2IDE0OS40NjEgNDUuNTg1OUMxNDkuNTgxIDQ1LjU3MDMgMTQ5LjY3NyA0NS41NTQ3IDE0OS43NSA0NS41MzkxTDE0OS43NTggNDYuOTc2NkMxNDkuNjAyIDQ3LjAyMzQgMTQ5LjQxOSA0Ny4wNjUxIDE0OS4yMTEgNDcuMTAxNkMxNDkuMDA4IDQ3LjEzOCAxNDguNzczIDQ3LjE1NjIgMTQ4LjUwOCA0Ny4xNTYyQzE0OC4wNzYgNDcuMTU2MiAxNDcuNjkzIDQ3LjA4MDcgMTQ3LjM1OSA0Ni45Mjk3QzE0Ny4wMjYgNDYuNzczNCAxNDYuNzY2IDQ2LjUyMDggMTQ2LjU3OCA0Ni4xNzE5QzE0Ni4zOTEgNDUuODIyOSAxNDYuMjk3IDQ1LjM1OTQgMTQ2LjI5NyA0NC43ODEyVjM2LjQ3NjZaTTE1Ni40NzcgNDUuMDA3OFYzOC41NDY5SDE1OC4zNjdWNDdIMTU2LjU4NkwxNTYuNDc3IDQ1LjAwNzhaTTE1Ni43NDIgNDMuMjVMMTU3LjM3NSA0My4yMzQ0QzE1Ny4zNzUgNDMuODAyMSAxNTcuMzEyIDQ0LjMyNTUgMTU3LjE4OCA0NC44MDQ3QzE1Ny4wNjIgNDUuMjc4NiAxNTYuODcgNDUuNjkyNyAxNTYuNjA5IDQ2LjA0NjlDMTU2LjM0OSA0Ni4zOTU4IDE1Ni4wMTYgNDYuNjY5MyAxNTUuNjA5IDQ2Ljg2NzJDMTU1LjIwMyA0Ny4wNTk5IDE1NC43MTYgNDcuMTU2MiAxNTQuMTQ4IDQ3LjE1NjJDMTUzLjczNyA0Ny4xNTYyIDE1My4zNTkgNDcuMDk2NCAxNTMuMDE2IDQ2Ljk3NjZDMTUyLjY3MiA0Ni44NTY4IDE1Mi4zNzUgNDYuNjcxOSAxNTIuMTI1IDQ2LjQyMTlDMTUxLjg4IDQ2LjE3MTkgMTUxLjY5IDQ1Ljg0NjQgMTUxLjU1NSA0NS40NDUzQzE1MS40MTkgNDUuMDQ0MyAxNTEuMzUyIDQ0LjU2NTEgMTUxLjM1MiA0NC4wMDc4VjM4LjU0NjlIMTUzLjIzNFY0NC4wMjM0QzE1My4yMzQgNDQuMzMwNyAxNTMuMjcxIDQ0LjU4ODUgMTUzLjM0NCA0NC43OTY5QzE1My40MTcgNDUgMTUzLjUxNiA0NS4xNjQxIDE1My42NDEgNDUuMjg5MUMxNTMuNzY2IDQ1LjQxNDEgMTUzLjkxMSA0NS41MDI2IDE1NC4wNzggNDUuNTU0N0MxNTQuMjQ1IDQ1LjYwNjggMTU0LjQyMiA0NS42MzI4IDE1NC42MDkgNDUuNjMyOEMxNTUuMTQ2IDQ1LjYzMjggMTU1LjU2OCA0NS41Mjg2IDE1NS44NzUgNDUuMzIwM0MxNTYuMTg4IDQ1LjEwNjggMTU2LjQwOSA0NC44MjAzIDE1Ni41MzkgNDQuNDYwOUMxNTYuNjc0IDQ0LjEwMTYgMTU2Ljc0MiA0My42OTc5IDE1Ni43NDIgNDMuMjVaTTE2Mi40MzggNDAuMTU2MlY0N0gxNjAuNTU1VjM4LjU0NjlIMTYyLjM1MkwxNjIuNDM4IDQwLjE1NjJaTTE2NS4wMjMgMzguNDkyMkwxNjUuMDA4IDQwLjI0MjJDMTY0Ljg5MyA0MC4yMjE0IDE2NC43NjggNDAuMjA1NyAxNjQuNjMzIDQwLjE5NTNDMTY0LjUwMyA0MC4xODQ5IDE2NC4zNzIgNDAuMTc5NyAxNjQuMjQyIDQwLjE3OTdDMTYzLjkxOSA0MC4xNzk3IDE2My42MzUgNDAuMjI2NiAxNjMuMzkxIDQwLjMyMDNDMTYzLjE0NiA0MC40MDg5IDE2Mi45NCA0MC41MzkxIDE2Mi43NzMgNDAuNzEwOUMxNjIuNjEyIDQwLjg3NzYgMTYyLjQ4NyA0MS4wODA3IDE2Mi4zOTggNDEuMzIwM0MxNjIuMzEgNDEuNTU5OSAxNjIuMjU4IDQxLjgyODEgMTYyLjI0MiA0Mi4xMjVMMTYxLjgxMiA0Mi4xNTYyQzE2MS44MTIgNDEuNjI1IDE2MS44NjUgNDEuMTMyOCAxNjEuOTY5IDQwLjY3OTdDMTYyLjA3MyA0MC4yMjY2IDE2Mi4yMjkgMzkuODI4MSAxNjIuNDM4IDM5LjQ4NDRDMTYyLjY1MSAzOS4xNDA2IDE2Mi45MTcgMzguODcyNCAxNjMuMjM0IDM4LjY3OTdDMTYzLjU1NyAzOC40ODcgMTYzLjkzIDM4LjM5MDYgMTY0LjM1MiAzOC4zOTA2QzE2NC40NjYgMzguMzkwNiAxNjQuNTg5IDM4LjQwMSAxNjQuNzE5IDM4LjQyMTlDMTY0Ljg1NCAzOC40NDI3IDE2NC45NTYgMzguNDY2MSAxNjUuMDIzIDM4LjQ5MjJaTTE3MC4wMjMgNDcuMTU2MkMxNjkuMzk4IDQ3LjE1NjIgMTY4LjgzMyA0Ny4wNTQ3IDE2OC4zMjggNDYuODUxNkMxNjcuODI4IDQ2LjY0MzIgMTY3LjQwMSA0Ni4zNTQyIDE2Ny4wNDcgNDUuOTg0NEMxNjYuNjk4IDQ1LjYxNDYgMTY2LjQzIDQ1LjE3OTcgMTY2LjI0MiA0NC42Nzk3QzE2Ni4wNTUgNDQuMTc5NyAxNjUuOTYxIDQzLjY0MDYgMTY1Ljk2MSA0My4wNjI1VjQyLjc1QzE2NS45NjEgNDIuMDg4NSAxNjYuMDU3IDQxLjQ4OTYgMTY2LjI1IDQwLjk1MzFDMTY2LjQ0MyA0MC40MTY3IDE2Ni43MTEgMzkuOTU4MyAxNjcuMDU1IDM5LjU3ODFDMTY3LjM5OCAzOS4xOTI3IDE2Ny44MDUgMzguODk4NCAxNjguMjczIDM4LjY5NTNDMTY4Ljc0MiAzOC40OTIyIDE2OS4yNSAzOC4zOTA2IDE2OS43OTcgMzguMzkwNkMxNzAuNDAxIDM4LjM5MDYgMTcwLjkzIDM4LjQ5MjIgMTcxLjM4MyAzOC42OTUzQzE3MS44MzYgMzguODk4NCAxNzIuMjExIDM5LjE4NDkgMTcyLjUwOCAzOS41NTQ3QzE3Mi44MSAzOS45MTkzIDE3My4wMzQgNDAuMzU0MiAxNzMuMTggNDAuODU5NEMxNzMuMzMxIDQxLjM2NDYgMTczLjQwNiA0MS45MjE5IDE3My40MDYgNDIuNTMxMlY0My4zMzU5SDE2Ni44NzVWNDEuOTg0NEgxNzEuNTQ3VjQxLjgzNTlDMTcxLjUzNiA0MS40OTc0IDE3MS40NjkgNDEuMTc5NyAxNzEuMzQ0IDQwLjg4MjhDMTcxLjIyNCA0MC41ODU5IDE3MS4wMzkgNDAuMzQ2NCAxNzAuNzg5IDQwLjE2NDFDMTcwLjUzOSAzOS45ODE4IDE3MC4yMDYgMzkuODkwNiAxNjkuNzg5IDM5Ljg5MDZDMTY5LjQ3NyAzOS44OTA2IDE2OS4xOTggMzkuOTU4MyAxNjguOTUzIDQwLjA5MzhDMTY4LjcxNCA0MC4yMjQgMTY4LjUxMyA0MC40MTQxIDE2OC4zNTIgNDAuNjY0MUMxNjguMTkgNDAuOTE0MSAxNjguMDY1IDQxLjIxNjEgMTY3Ljk3NyA0MS41NzAzQzE2Ny44OTMgNDEuOTE5MyAxNjcuODUyIDQyLjMxMjUgMTY3Ljg1MiA0Mi43NVY0My4wNjI1QzE2Ny44NTIgNDMuNDMyMyAxNjcuOTAxIDQzLjc3NiAxNjggNDQuMDkzOEMxNjguMTA0IDQ0LjQwNjIgMTY4LjI1NSA0NC42Nzk3IDE2OC40NTMgNDQuOTE0MUMxNjguNjUxIDQ1LjE0ODQgMTY4Ljg5MSA0NS4zMzMzIDE2OS4xNzIgNDUuNDY4OEMxNjkuNDUzIDQ1LjU5OSAxNjkuNzczIDQ1LjY2NDEgMTcwLjEzMyA0NS42NjQxQzE3MC41ODYgNDUuNjY0MSAxNzAuOTkgNDUuNTcyOSAxNzEuMzQ0IDQ1LjM5MDZDMTcxLjY5OCA0NS4yMDgzIDE3Mi4wMDUgNDQuOTUwNSAxNzIuMjY2IDQ0LjYxNzJMMTczLjI1OCA0NS41NzgxQzE3My4wNzYgNDUuODQzOCAxNzIuODM5IDQ2LjA5OSAxNzIuNTQ3IDQ2LjM0MzhDMTcyLjI1NSA0Ni41ODMzIDE3MS44OTggNDYuNzc4NiAxNzEuNDc3IDQ2LjkyOTdDMTcxLjA2IDQ3LjA4MDcgMTcwLjU3NiA0Ny4xNTYyIDE3MC4wMjMgNDcuMTU2MloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuODciLz4KPHBhdGggZD0iTTg2LjIxMDkgNjQuODM0VjY2SDgxLjkyNzdWNjQuODM0SDg2LjIxMDlaTTgyLjMzNzkgNTcuNDY4OFY2Nkg4MC44NjcyVjU3LjQ2ODhIODIuMzM3OVpNOTEuMDMxMiA2NC43Mjg1VjYxLjcwNTFDOTEuMDMxMiA2MS40Nzg1IDkwLjk5MDIgNjEuMjgzMiA5MC45MDgyIDYxLjExOTFDOTAuODI2MiA2MC45NTUxIDkwLjcwMTIgNjAuODI4MSA5MC41MzMyIDYwLjczODNDOTAuMzY5MSA2MC42NDg0IDkwLjE2MjEgNjAuNjAzNSA4OS45MTIxIDYwLjYwMzVDODkuNjgxNiA2MC42MDM1IDg5LjQ4MjQgNjAuNjQyNiA4OS4zMTQ1IDYwLjcyMDdDODkuMTQ2NSA2MC43OTg4IDg5LjAxNTYgNjAuOTA0MyA4OC45MjE5IDYxLjAzNzFDODguODI4MSA2MS4xNjk5IDg4Ljc4MTIgNjEuMzIwMyA4OC43ODEyIDYxLjQ4ODNIODcuMzc1Qzg3LjM3NSA2MS4yMzgzIDg3LjQzNTUgNjAuOTk2MSA4Ny41NTY2IDYwLjc2MTdDODcuNjc3NyA2MC41MjczIDg3Ljg1MzUgNjAuMzE4NCA4OC4wODQgNjAuMTM0OEM4OC4zMTQ1IDU5Ljk1MTIgODguNTg5OCA1OS44MDY2IDg4LjkxMDIgNTkuNzAxMkM4OS4yMzA1IDU5LjU5NTcgODkuNTg5OCA1OS41NDMgODkuOTg4MyA1OS41NDNDOTAuNDY0OCA1OS41NDMgOTAuODg2NyA1OS42MjMgOTEuMjUzOSA1OS43ODMyQzkxLjYyNSA1OS45NDM0IDkxLjkxNiA2MC4xODU1IDkyLjEyNyA2MC41MDk4QzkyLjM0MTggNjAuODMwMSA5Mi40NDkyIDYxLjIzMjQgOTIuNDQ5MiA2MS43MTY4VjY0LjUzNTJDOTIuNDQ5MiA2NC44MjQyIDkyLjQ2ODggNjUuMDg0IDkyLjUwNzggNjUuMzE0NUM5Mi41NTA4IDY1LjU0MSA5Mi42MTEzIDY1LjczODMgOTIuNjg5NSA2NS45MDYyVjY2SDkxLjI0MjJDOTEuMTc1OCA2NS44NDc3IDkxLjEyMyA2NS42NTQzIDkxLjA4NCA2NS40MTk5QzkxLjA0ODggNjUuMTgxNiA5MS4wMzEyIDY0Ljk1MTIgOTEuMDMxMiA2NC43Mjg1Wk05MS4yMzYzIDYyLjE0NDVMOTEuMjQ4IDYzLjAxNzZIOTAuMjM0NEM4OS45NzI3IDYzLjAxNzYgODkuNzQyMiA2My4wNDMgODkuNTQzIDYzLjA5MzhDODkuMzQzOCA2My4xNDA2IDg5LjE3NzcgNjMuMjEwOSA4OS4wNDQ5IDYzLjMwNDdDODguOTEyMSA2My4zOTg0IDg4LjgxMjUgNjMuNTExNyA4OC43NDYxIDYzLjY0NDVDODguNjc5NyA2My43NzczIDg4LjY0NjUgNjMuOTI3NyA4OC42NDY1IDY0LjA5NTdDODguNjQ2NSA2NC4yNjM3IDg4LjY4NTUgNjQuNDE4IDg4Ljc2MzcgNjQuNTU4NkM4OC44NDE4IDY0LjY5NTMgODguOTU1MSA2NC44MDI3IDg5LjEwMzUgNjQuODgwOUM4OS4yNTU5IDY0Ljk1OSA4OS40Mzk1IDY0Ljk5OCA4OS42NTQzIDY0Ljk5OEM4OS45NDM0IDY0Ljk5OCA5MC4xOTUzIDY0LjkzOTUgOTAuNDEwMiA2NC44MjIzQzkwLjYyODkgNjQuNzAxMiA5MC44MDA4IDY0LjU1NDcgOTAuOTI1OCA2NC4zODI4QzkxLjA1MDggNjQuMjA3IDkxLjExNzIgNjQuMDQxIDkxLjEyNSA2My44ODQ4TDkxLjU4MiA2NC41MTE3QzkxLjUzNTIgNjQuNjcxOSA5MS40NTUxIDY0Ljg0MzggOTEuMzQxOCA2NS4wMjczQzkxLjIyODUgNjUuMjEwOSA5MS4wODAxIDY1LjM4NjcgOTAuODk2NSA2NS41NTQ3QzkwLjcxNjggNjUuNzE4OCA5MC41IDY1Ljg1MzUgOTAuMjQ2MSA2NS45NTlDODkuOTk2MSA2Ni4wNjQ1IDg5LjcwNyA2Ni4xMTcyIDg5LjM3ODkgNjYuMTE3MkM4OC45NjQ4IDY2LjExNzIgODguNTk1NyA2Ni4wMzUyIDg4LjI3MTUgNjUuODcxMUM4Ny45NDczIDY1LjcwMzEgODcuNjkzNCA2NS40Nzg1IDg3LjUwOTggNjUuMTk3M0M4Ny4zMjYyIDY0LjkxMjEgODcuMjM0NCA2NC41ODk4IDg3LjIzNDQgNjQuMjMwNUM4Ny4yMzQ0IDYzLjg5NDUgODcuMjk2OSA2My41OTc3IDg3LjQyMTkgNjMuMzM5OEM4Ny41NTA4IDYzLjA3ODEgODcuNzM4MyA2Mi44NTk0IDg3Ljk4NDQgNjIuNjgzNkM4OC4yMzQ0IDYyLjUwNzggODguNTM5MSA2Mi4zNzUgODguODk4NCA2Mi4yODUyQzg5LjI1NzggNjIuMTkxNCA4OS42NjggNjIuMTQ0NSA5MC4xMjg5IDYyLjE0NDVIOTEuMjM2M1pNOTcuNzMyNCA2NC4yODMyQzk3LjczMjQgNjQuMTQyNiA5Ny42OTczIDY0LjAxNTYgOTcuNjI3IDYzLjkwMjNDOTcuNTU2NiA2My43ODUyIDk3LjQyMTkgNjMuNjc5NyA5Ny4yMjI3IDYzLjU4NTlDOTcuMDI3MyA2My40OTIyIDk2LjczODMgNjMuNDA2MiA5Ni4zNTU1IDYzLjMyODFDOTYuMDE5NSA2My4yNTM5IDk1LjcxMDkgNjMuMTY2IDk1LjQyOTcgNjMuMDY0NUM5NS4xNTIzIDYyLjk1OSA5NC45MTQxIDYyLjgzMiA5NC43MTQ4IDYyLjY4MzZDOTQuNTE1NiA2Mi41MzUyIDk0LjM2MTMgNjIuMzU5NCA5NC4yNTIgNjIuMTU2MkM5NC4xNDI2IDYxLjk1MzEgOTQuMDg3OSA2MS43MTg4IDk0LjA4NzkgNjEuNDUzMUM5NC4wODc5IDYxLjE5NTMgOTQuMTQ0NSA2MC45NTEyIDk0LjI1NzggNjAuNzIwN0M5NC4zNzExIDYwLjQ5MDIgOTQuNTMzMiA2MC4yODcxIDk0Ljc0NDEgNjAuMTExM0M5NC45NTUxIDU5LjkzNTUgOTUuMjEwOSA1OS43OTY5IDk1LjUxMTcgNTkuNjk1M0M5NS44MTY0IDU5LjU5MzggOTYuMTU2MiA1OS41NDMgOTYuNTMxMiA1OS41NDNDOTcuMDYyNSA1OS41NDMgOTcuNTE3NiA1OS42MzI4IDk3Ljg5NjUgNTkuODEyNUM5OC4yNzkzIDU5Ljk4ODMgOTguNTcyMyA2MC4yMjg1IDk4Ljc3NTQgNjAuNTMzMkM5OC45Nzg1IDYwLjgzNCA5OS4wODAxIDYxLjE3MzggOTkuMDgwMSA2MS41NTI3SDk3LjY2OEM5Ny42NjggNjEuMzg0OCA5Ny42MjUgNjEuMjI4NSA5Ny41MzkxIDYxLjA4NEM5Ny40NTcgNjAuOTM1NSA5Ny4zMzIgNjAuODE2NCA5Ny4xNjQxIDYwLjcyNjZDOTYuOTk2MSA2MC42MzI4IDk2Ljc4NTIgNjAuNTg1OSA5Ni41MzEyIDYwLjU4NTlDOTYuMjg5MSA2MC41ODU5IDk2LjA4NzkgNjAuNjI1IDk1LjkyNzcgNjAuNzAzMUM5NS43NzE1IDYwLjc3NzMgOTUuNjU0MyA2MC44NzUgOTUuNTc2MiA2MC45OTYxQzk1LjUwMiA2MS4xMTcyIDk1LjQ2NDggNjEuMjUgOTUuNDY0OCA2MS4zOTQ1Qzk1LjQ2NDggNjEuNSA5NS40ODQ0IDYxLjU5NTcgOTUuNTIzNCA2MS42ODE2Qzk1LjU2NjQgNjEuNzYzNyA5NS42MzY3IDYxLjgzOTggOTUuNzM0NCA2MS45MTAyQzk1LjgzMiA2MS45NzY2IDk1Ljk2NDggNjIuMDM5MSA5Ni4xMzI4IDYyLjA5NzdDOTYuMzA0NyA2Mi4xNTYyIDk2LjUxOTUgNjIuMjEyOSA5Ni43NzczIDYyLjI2NzZDOTcuMjYxNyA2Mi4zNjkxIDk3LjY3NzcgNjIuNSA5OC4wMjU0IDYyLjY2MDJDOTguMzc3IDYyLjgxNjQgOTguNjQ2NSA2My4wMTk1IDk4LjgzNCA2My4yNjk1Qzk5LjAyMTUgNjMuNTE1NiA5OS4xMTUyIDYzLjgyODEgOTkuMTE1MiA2NC4yMDdDOTkuMTE1MiA2NC40ODgzIDk5LjA1NDcgNjQuNzQ2MSA5OC45MzM2IDY0Ljk4MDVDOTguODE2NCA2NS4yMTA5IDk4LjY0NDUgNjUuNDEyMSA5OC40MTggNjUuNTg0Qzk4LjE5MTQgNjUuNzUyIDk3LjkxOTkgNjUuODgyOCA5Ny42MDM1IDY1Ljk3NjZDOTcuMjkxIDY2LjA3MDMgOTYuOTM5NSA2Ni4xMTcyIDk2LjU0ODggNjYuMTE3MkM5NS45NzQ2IDY2LjExNzIgOTUuNDg4MyA2Ni4wMTU2IDk1LjA4OTggNjUuODEyNUM5NC42OTE0IDY1LjYwNTUgOTQuMzg4NyA2NS4zNDE4IDk0LjE4MTYgNjUuMDIxNUM5My45Nzg1IDY0LjY5NzMgOTMuODc3IDY0LjM2MTMgOTMuODc3IDY0LjAxMzdIOTUuMjQyMkM5NS4yNTc4IDY0LjI3NTQgOTUuMzMwMSA2NC40ODQ0IDk1LjQ1OSA2NC42NDA2Qzk1LjU5MTggNjQuNzkzIDk1Ljc1NTkgNjQuOTA0MyA5NS45NTEyIDY0Ljk3NDZDOTYuMTUwNCA2NS4wNDEgOTYuMzU1NSA2NS4wNzQyIDk2LjU2NjQgNjUuMDc0MkM5Ni44MjAzIDY1LjA3NDIgOTcuMDMzMiA2NS4wNDEgOTcuMjA1MSA2NC45NzQ2Qzk3LjM3NyA2NC45MDQzIDk3LjUwNzggNjQuODEwNSA5Ny41OTc3IDY0LjY5MzRDOTcuNjg3NSA2NC41NzIzIDk3LjczMjQgNjQuNDM1NSA5Ny43MzI0IDY0LjI4MzJaTTEwMy41MDggNTkuNjYwMlY2MC42OTE0SDk5LjkzMzZWNTkuNjYwMkgxMDMuNTA4Wk0xMDAuOTY1IDU4LjEwNzRIMTAyLjM3N1Y2NC4yNDhDMTAyLjM3NyA2NC40NDM0IDEwMi40MDQgNjQuNTkzOCAxMDIuNDU5IDY0LjY5OTJDMTAyLjUxOCA2NC44MDA4IDEwMi41OTggNjQuODY5MSAxMDIuNjk5IDY0LjkwNDNDMTAyLjgwMSA2NC45Mzk1IDEwMi45MiA2NC45NTcgMTAzLjA1NyA2NC45NTdDMTAzLjE1NCA2NC45NTcgMTAzLjI0OCA2NC45NTEyIDEwMy4zMzggNjQuOTM5NUMxMDMuNDI4IDY0LjkyNzcgMTAzLjUgNjQuOTE2IDEwMy41NTUgNjQuOTA0M0wxMDMuNTYxIDY1Ljk4MjRDMTAzLjQ0MyA2Ni4wMTc2IDEwMy4zMDcgNjYuMDQ4OCAxMDMuMTUgNjYuMDc2MkMxMDIuOTk4IDY2LjEwMzUgMTAyLjgyMiA2Ni4xMTcyIDEwMi42MjMgNjYuMTE3MkMxMDIuMjk5IDY2LjExNzIgMTAyLjAxMiA2Ni4wNjA1IDEwMS43NjIgNjUuOTQ3M0MxMDEuNTEyIDY1LjgzMDEgMTAxLjMxNiA2NS42NDA2IDEwMS4xNzYgNjUuMzc4OUMxMDEuMDM1IDY1LjExNzIgMTAwLjk2NSA2NC43Njk1IDEwMC45NjUgNjQuMzM1OVY1OC4xMDc0Wk0xMTEuOSA2NC41MDU5VjU5LjY2MDJIMTEzLjMxOFY2NkgxMTEuOTgyTDExMS45IDY0LjUwNTlaTTExMi4xIDYzLjE4NzVMMTEyLjU3NCA2My4xNzU4QzExMi41NzQgNjMuNjAxNiAxMTIuNTI3IDYzLjk5NDEgMTEyLjQzNCA2NC4zNTM1QzExMi4zNCA2NC43MDkgMTEyLjE5NSA2NS4wMTk1IDExMiA2NS4yODUyQzExMS44MDUgNjUuNTQ2OSAxMTEuNTU1IDY1Ljc1MiAxMTEuMjUgNjUuOTAwNEMxMTAuOTQ1IDY2LjA0NDkgMTEwLjU4IDY2LjExNzIgMTEwLjE1NCA2Ni4xMTcyQzEwOS44NDYgNjYuMTE3MiAxMDkuNTYyIDY2LjA3MjMgMTA5LjMwNSA2NS45ODI0QzEwOS4wNDcgNjUuODkyNiAxMDguODI0IDY1Ljc1MzkgMTA4LjYzNyA2NS41NjY0QzEwOC40NTMgNjUuMzc4OSAxMDguMzExIDY1LjEzNDggMTA4LjIwOSA2NC44MzRDMTA4LjEwNyA2NC41MzMyIDEwOC4wNTcgNjQuMTczOCAxMDguMDU3IDYzLjc1NTlWNTkuNjYwMkgxMDkuNDY5VjYzLjc2NzZDMTA5LjQ2OSA2My45OTggMTA5LjQ5NiA2NC4xOTE0IDEwOS41NTEgNjQuMzQ3N0MxMDkuNjA1IDY0LjUgMTA5LjY4IDY0LjYyMyAxMDkuNzczIDY0LjcxNjhDMTA5Ljg2NyA2NC44MTA1IDEwOS45NzcgNjQuODc3IDExMC4xMDIgNjQuOTE2QzExMC4yMjcgNjQuOTU1MSAxMTAuMzU5IDY0Ljk3NDYgMTEwLjUgNjQuOTc0NkMxMTAuOTAyIDY0Ljk3NDYgMTExLjIxOSA2NC44OTY1IDExMS40NDkgNjQuNzQwMkMxMTEuNjg0IDY0LjU4MDEgMTExLjg1IDY0LjM2NTIgMTExLjk0NyA2NC4wOTU3QzExMi4wNDkgNjMuODI2MiAxMTIuMSA2My41MjM0IDExMi4xIDYzLjE4NzVaTTExNi40MzQgNjAuODc4OVY2OC40Mzc1SDExNS4wMjFWNTkuNjYwMkgxMTYuMzIyTDExNi40MzQgNjAuODc4OVpNMTIwLjU2NCA2Mi43NzE1VjYyLjg5NDVDMTIwLjU2NCA2My4zNTU1IDEyMC41MSA2My43ODMyIDEyMC40IDY0LjE3NzdDMTIwLjI5NSA2NC41Njg0IDEyMC4xMzcgNjQuOTEwMiAxMTkuOTI2IDY1LjIwMzFDMTE5LjcxOSA2NS40OTIyIDExOS40NjMgNjUuNzE2OCAxMTkuMTU4IDY1Ljg3N0MxMTguODU0IDY2LjAzNzEgMTE4LjUwMiA2Ni4xMTcyIDExOC4xMDQgNjYuMTE3MkMxMTcuNzA5IDY2LjExNzIgMTE3LjM2MyA2Ni4wNDQ5IDExNy4wNjYgNjUuOTAwNEMxMTYuNzczIDY1Ljc1MiAxMTYuNTI1IDY1LjU0MyAxMTYuMzIyIDY1LjI3MzRDMTE2LjExOSA2NS4wMDM5IDExNS45NTUgNjQuNjg3NSAxMTUuODMgNjQuMzI0MkMxMTUuNzA5IDYzLjk1NyAxMTUuNjIzIDYzLjU1NDcgMTE1LjU3MiA2My4xMTcyVjYyLjY0MjZDMTE1LjYyMyA2Mi4xNzc3IDExNS43MDkgNjEuNzU1OSAxMTUuODMgNjEuMzc3QzExNS45NTUgNjAuOTk4IDExNi4xMTkgNjAuNjcxOSAxMTYuMzIyIDYwLjM5ODRDMTE2LjUyNSA2MC4xMjUgMTE2Ljc3MyA1OS45MTQxIDExNy4wNjYgNTkuNzY1NkMxMTcuMzU5IDU5LjYxNzIgMTE3LjcwMSA1OS41NDMgMTE4LjA5MiA1OS41NDNDMTE4LjQ5IDU5LjU0MyAxMTguODQ0IDU5LjYyMTEgMTE5LjE1MiA1OS43NzczQzExOS40NjEgNTkuOTI5NyAxMTkuNzIxIDYwLjE0ODQgMTE5LjkzMiA2MC40MzM2QzEyMC4xNDMgNjAuNzE0OCAxMjAuMzAxIDYxLjA1NDcgMTIwLjQwNiA2MS40NTMxQzEyMC41MTIgNjEuODQ3NyAxMjAuNTY0IDYyLjI4NzEgMTIwLjU2NCA2Mi43NzE1Wk0xMTkuMTUyIDYyLjg5NDVWNjIuNzcxNUMxMTkuMTUyIDYyLjQ3ODUgMTE5LjEyNSA2Mi4yMDcgMTE5LjA3IDYxLjk1N0MxMTkuMDE2IDYxLjcwMzEgMTE4LjkzIDYxLjQ4MDUgMTE4LjgxMiA2MS4yODkxQzExOC42OTUgNjEuMDk3NyAxMTguNTQ1IDYwLjk0OTIgMTE4LjM2MSA2MC44NDM4QzExOC4xODIgNjAuNzM0NCAxMTcuOTY1IDYwLjY3OTcgMTE3LjcxMSA2MC42Nzk3QzExNy40NjEgNjAuNjc5NyAxMTcuMjQ2IDYwLjcyMjcgMTE3LjA2NiA2MC44MDg2QzExNi44ODcgNjAuODkwNiAxMTYuNzM2IDYxLjAwNTkgMTE2LjYxNSA2MS4xNTQzQzExNi40OTQgNjEuMzAyNyAxMTYuNCA2MS40NzY2IDExNi4zMzQgNjEuNjc1OEMxMTYuMjY4IDYxLjg3MTEgMTE2LjIyMSA2Mi4wODQgMTE2LjE5MyA2Mi4zMTQ1VjYzLjQ1MTJDMTE2LjI0IDYzLjczMjQgMTE2LjMyIDYzLjk5MDIgMTE2LjQzNCA2NC4yMjQ2QzExNi41NDcgNjQuNDU5IDExNi43MDcgNjQuNjQ2NSAxMTYuOTE0IDY0Ljc4NzFDMTE3LjEyNSA2NC45MjM4IDExNy4zOTUgNjQuOTkyMiAxMTcuNzIzIDY0Ljk5MjJDMTE3Ljk3NyA2NC45OTIyIDExOC4xOTMgNjQuOTM3NSAxMTguMzczIDY0LjgyODFDMTE4LjU1MyA2NC43MTg4IDExOC42OTkgNjQuNTY4NCAxMTguODEyIDY0LjM3N0MxMTguOTMgNjQuMTgxNiAxMTkuMDE2IDYzLjk1NyAxMTkuMDcgNjMuNzAzMUMxMTkuMTI1IDYzLjQ0OTIgMTE5LjE1MiA2My4xNzk3IDExOS4xNTIgNjIuODk0NVpNMTI1Ljg4MyA2NC42ODc1VjU3SDEyNy4zMDFWNjZIMTI2LjAxOEwxMjUuODgzIDY0LjY4NzVaTTEyMS43NTggNjIuOTAwNFY2Mi43NzczQzEyMS43NTggNjIuMjk2OSAxMjEuODE0IDYxLjg1OTQgMTIxLjkyOCA2MS40NjQ4QzEyMi4wNDEgNjEuMDY2NCAxMjIuMjA1IDYwLjcyNDYgMTIyLjQyIDYwLjQzOTVDMTIyLjYzNSA2MC4xNTA0IDEyMi44OTYgNTkuOTI5NyAxMjMuMjA1IDU5Ljc3NzNDMTIzLjUxNCA1OS42MjExIDEyMy44NjEgNTkuNTQzIDEyNC4yNDggNTkuNTQzQzEyNC42MzEgNTkuNTQzIDEyNC45NjcgNTkuNjE3MiAxMjUuMjU2IDU5Ljc2NTZDMTI1LjU0NSA1OS45MTQxIDEyNS43OTEgNjAuMTI3IDEyNS45OTQgNjAuNDA0M0MxMjYuMTk3IDYwLjY3NzcgMTI2LjM1OSA2MS4wMDU5IDEyNi40OCA2MS4zODg3QzEyNi42MDIgNjEuNzY3NiAxMjYuNjg4IDYyLjE4OTUgMTI2LjczOCA2Mi42NTQzVjYzLjA0NjlDMTI2LjY4OCA2My41IDEyNi42MDIgNjMuOTE0MSAxMjYuNDggNjQuMjg5MUMxMjYuMzU5IDY0LjY2NDEgMTI2LjE5NyA2NC45ODgzIDEyNS45OTQgNjUuMjYxN0MxMjUuNzkxIDY1LjUzNTIgMTI1LjU0MyA2NS43NDYxIDEyNS4yNSA2NS44OTQ1QzEyNC45NjEgNjYuMDQzIDEyNC42MjMgNjYuMTE3MiAxMjQuMjM2IDY2LjExNzJDMTIzLjg1NCA2Ni4xMTcyIDEyMy41MDggNjYuMDM3MSAxMjMuMTk5IDY1Ljg3N0MxMjIuODk1IDY1LjcxNjggMTIyLjYzNSA2NS40OTIyIDEyMi40MiA2NS4yMDMxQzEyMi4yMDUgNjQuOTE0MSAxMjIuMDQxIDY0LjU3NDIgMTIxLjkyOCA2NC4xODM2QzEyMS44MTQgNjMuNzg5MSAxMjEuNzU4IDYzLjM2MTMgMTIxLjc1OCA2Mi45MDA0Wk0xMjMuMTcgNjIuNzc3M1Y2Mi45MDA0QzEyMy4xNyA2My4xODk1IDEyMy4xOTUgNjMuNDU5IDEyMy4yNDYgNjMuNzA5QzEyMy4zMDEgNjMuOTU5IDEyMy4zODUgNjQuMTc5NyAxMjMuNDk4IDY0LjM3MTFDMTIzLjYxMSA2NC41NTg2IDEyMy43NTggNjQuNzA3IDEyMy45MzggNjQuODE2NEMxMjQuMTIxIDY0LjkyMTkgMTI0LjM0IDY0Ljk3NDYgMTI0LjU5NCA2NC45NzQ2QzEyNC45MTQgNjQuOTc0NiAxMjUuMTc4IDY0LjkwNDMgMTI1LjM4NSA2NC43NjM3QzEyNS41OTIgNjQuNjIzIDEyNS43NTQgNjQuNDMzNiAxMjUuODcxIDY0LjE5NTNDMTI1Ljk5MiA2My45NTMxIDEyNi4wNzQgNjMuNjgzNiAxMjYuMTE3IDYzLjM4NjdWNjIuMzI2MkMxMjYuMDk0IDYyLjA5NTcgMTI2LjA0NSA2MS44ODA5IDEyNS45NzEgNjEuNjgxNkMxMjUuOSA2MS40ODI0IDEyNS44MDUgNjEuMzA4NiAxMjUuNjg0IDYxLjE2MDJDMTI1LjU2MiA2MS4wMDc4IDEyNS40MTIgNjAuODkwNiAxMjUuMjMyIDYwLjgwODZDMTI1LjA1NyA2MC43MjI3IDEyNC44NDggNjAuNjc5NyAxMjQuNjA1IDYwLjY3OTdDMTI0LjM0OCA2MC42Nzk3IDEyNC4xMjkgNjAuNzM0NCAxMjMuOTQ5IDYwLjg0MzhDMTIzLjc3IDYwLjk1MzEgMTIzLjYyMSA2MS4xMDM1IDEyMy41MDQgNjEuMjk0OUMxMjMuMzkxIDYxLjQ4NjMgMTIzLjMwNyA2MS43MDkgMTIzLjI1MiA2MS45NjI5QzEyMy4xOTcgNjIuMjE2OCAxMjMuMTcgNjIuNDg4MyAxMjMuMTcgNjIuNzc3M1pNMTMyLjYwMiA2NC43Mjg1VjYxLjcwNTFDMTMyLjYwMiA2MS40Nzg1IDEzMi41NjEgNjEuMjgzMiAxMzIuNDc5IDYxLjExOTFDMTMyLjM5NiA2MC45NTUxIDEzMi4yNzEgNjAuODI4MSAxMzIuMTA0IDYwLjczODNDMTMxLjkzOSA2MC42NDg0IDEzMS43MzIgNjAuNjAzNSAxMzEuNDgyIDYwLjYwMzVDMTMxLjI1MiA2MC42MDM1IDEzMS4wNTMgNjAuNjQyNiAxMzAuODg1IDYwLjcyMDdDMTMwLjcxNyA2MC43OTg4IDEzMC41ODYgNjAuOTA0MyAxMzAuNDkyIDYxLjAzNzFDMTMwLjM5OCA2MS4xNjk5IDEzMC4zNTIgNjEuMzIwMyAxMzAuMzUyIDYxLjQ4ODNIMTI4Ljk0NUMxMjguOTQ1IDYxLjIzODMgMTI5LjAwNiA2MC45OTYxIDEyOS4xMjcgNjAuNzYxN0MxMjkuMjQ4IDYwLjUyNzMgMTI5LjQyNCA2MC4zMTg0IDEyOS42NTQgNjAuMTM0OEMxMjkuODg1IDU5Ljk1MTIgMTMwLjE2IDU5LjgwNjYgMTMwLjQ4IDU5LjcwMTJDMTMwLjgwMSA1OS41OTU3IDEzMS4xNiA1OS41NDMgMTMxLjU1OSA1OS41NDNDMTMyLjAzNSA1OS41NDMgMTMyLjQ1NyA1OS42MjMgMTMyLjgyNCA1OS43ODMyQzEzMy4xOTUgNTkuOTQzNCAxMzMuNDg2IDYwLjE4NTUgMTMzLjY5NyA2MC41MDk4QzEzMy45MTIgNjAuODMwMSAxMzQuMDIgNjEuMjMyNCAxMzQuMDIgNjEuNzE2OFY2NC41MzUyQzEzNC4wMiA2NC44MjQyIDEzNC4wMzkgNjUuMDg0IDEzNC4wNzggNjUuMzE0NUMxMzQuMTIxIDY1LjU0MSAxMzQuMTgyIDY1LjczODMgMTM0LjI2IDY1LjkwNjJWNjZIMTMyLjgxMkMxMzIuNzQ2IDY1Ljg0NzcgMTMyLjY5MyA2NS42NTQzIDEzMi42NTQgNjUuNDE5OUMxMzIuNjE5IDY1LjE4MTYgMTMyLjYwMiA2NC45NTEyIDEzMi42MDIgNjQuNzI4NVpNMTMyLjgwNyA2Mi4xNDQ1TDEzMi44MTggNjMuMDE3NkgxMzEuODA1QzEzMS41NDMgNjMuMDE3NiAxMzEuMzEyIDYzLjA0MyAxMzEuMTEzIDYzLjA5MzhDMTMwLjkxNCA2My4xNDA2IDEzMC43NDggNjMuMjEwOSAxMzAuNjE1IDYzLjMwNDdDMTMwLjQ4MiA2My4zOTg0IDEzMC4zODMgNjMuNTExNyAxMzAuMzE2IDYzLjY0NDVDMTMwLjI1IDYzLjc3NzMgMTMwLjIxNyA2My45Mjc3IDEzMC4yMTcgNjQuMDk1N0MxMzAuMjE3IDY0LjI2MzcgMTMwLjI1NiA2NC40MTggMTMwLjMzNCA2NC41NTg2QzEzMC40MTIgNjQuNjk1MyAxMzAuNTI1IDY0LjgwMjcgMTMwLjY3NCA2NC44ODA5QzEzMC44MjYgNjQuOTU5IDEzMS4wMSA2NC45OTggMTMxLjIyNSA2NC45OThDMTMxLjUxNCA2NC45OTggMTMxLjc2NiA2NC45Mzk1IDEzMS45OCA2NC44MjIzQzEzMi4xOTkgNjQuNzAxMiAxMzIuMzcxIDY0LjU1NDcgMTMyLjQ5NiA2NC4zODI4QzEzMi42MjEgNjQuMjA3IDEzMi42ODggNjQuMDQxIDEzMi42OTUgNjMuODg0OEwxMzMuMTUyIDY0LjUxMTdDMTMzLjEwNSA2NC42NzE5IDEzMy4wMjUgNjQuODQzOCAxMzIuOTEyIDY1LjAyNzNDMTMyLjc5OSA2NS4yMTA5IDEzMi42NSA2NS4zODY3IDEzMi40NjcgNjUuNTU0N0MxMzIuMjg3IDY1LjcxODggMTMyLjA3IDY1Ljg1MzUgMTMxLjgxNiA2NS45NTlDMTMxLjU2NiA2Ni4wNjQ1IDEzMS4yNzcgNjYuMTE3MiAxMzAuOTQ5IDY2LjExNzJDMTMwLjUzNSA2Ni4xMTcyIDEzMC4xNjYgNjYuMDM1MiAxMjkuODQyIDY1Ljg3MTFDMTI5LjUxOCA2NS43MDMxIDEyOS4yNjQgNjUuNDc4NSAxMjkuMDggNjUuMTk3M0MxMjguODk2IDY0LjkxMjEgMTI4LjgwNSA2NC41ODk4IDEyOC44MDUgNjQuMjMwNUMxMjguODA1IDYzLjg5NDUgMTI4Ljg2NyA2My41OTc3IDEyOC45OTIgNjMuMzM5OEMxMjkuMTIxIDYzLjA3ODEgMTI5LjMwOSA2Mi44NTk0IDEyOS41NTUgNjIuNjgzNkMxMjkuODA1IDYyLjUwNzggMTMwLjEwOSA2Mi4zNzUgMTMwLjQ2OSA2Mi4yODUyQzEzMC44MjggNjIuMTkxNCAxMzEuMjM4IDYyLjE0NDUgMTMxLjY5OSA2Mi4xNDQ1SDEzMi44MDdaTTEzOC42NTIgNTkuNjYwMlY2MC42OTE0SDEzNS4wNzhWNTkuNjYwMkgxMzguNjUyWk0xMzYuMTA5IDU4LjEwNzRIMTM3LjUyMVY2NC4yNDhDMTM3LjUyMSA2NC40NDM0IDEzNy41NDkgNjQuNTkzOCAxMzcuNjA0IDY0LjY5OTJDMTM3LjY2MiA2NC44MDA4IDEzNy43NDIgNjQuODY5MSAxMzcuODQ0IDY0LjkwNDNDMTM3Ljk0NSA2NC45Mzk1IDEzOC4wNjQgNjQuOTU3IDEzOC4yMDEgNjQuOTU3QzEzOC4yOTkgNjQuOTU3IDEzOC4zOTMgNjQuOTUxMiAxMzguNDgyIDY0LjkzOTVDMTM4LjU3MiA2NC45Mjc3IDEzOC42NDUgNjQuOTE2IDEzOC42OTkgNjQuOTA0M0wxMzguNzA1IDY1Ljk4MjRDMTM4LjU4OCA2Ni4wMTc2IDEzOC40NTEgNjYuMDQ4OCAxMzguMjk1IDY2LjA3NjJDMTM4LjE0MyA2Ni4xMDM1IDEzNy45NjcgNjYuMTE3MiAxMzcuNzY4IDY2LjExNzJDMTM3LjQ0MyA2Ni4xMTcyIDEzNy4xNTYgNjYuMDYwNSAxMzYuOTA2IDY1Ljk0NzNDMTM2LjY1NiA2NS44MzAxIDEzNi40NjEgNjUuNjQwNiAxMzYuMzIgNjUuMzc4OUMxMzYuMTggNjUuMTE3MiAxMzYuMTA5IDY0Ljc2OTUgMTM2LjEwOSA2NC4zMzU5VjU4LjEwNzRaTTE0Mi43ODcgNjYuMTE3MkMxNDIuMzE4IDY2LjExNzIgMTQxLjg5NSA2Ni4wNDEgMTQxLjUxNiA2NS44ODg3QzE0MS4xNDEgNjUuNzMyNCAxNDAuODIgNjUuNTE1NiAxNDAuNTU1IDY1LjIzODNDMTQwLjI5MyA2NC45NjA5IDE0MC4wOTIgNjQuNjM0OCAxMzkuOTUxIDY0LjI1OThDMTM5LjgxMSA2My44ODQ4IDEzOS43NCA2My40ODA1IDEzOS43NCA2My4wNDY5VjYyLjgxMjVDMTM5Ljc0IDYyLjMxNjQgMTM5LjgxMiA2MS44NjcyIDEzOS45NTcgNjEuNDY0OEMxNDAuMTAyIDYxLjA2MjUgMTQwLjMwMyA2MC43MTg4IDE0MC41NjEgNjAuNDMzNkMxNDAuODE4IDYwLjE0NDUgMTQxLjEyMyA1OS45MjM4IDE0MS40NzUgNTkuNzcxNUMxNDEuODI2IDU5LjYxOTEgMTQyLjIwNyA1OS41NDMgMTQyLjYxNyA1OS41NDNDMTQzLjA3IDU5LjU0MyAxNDMuNDY3IDU5LjYxOTEgMTQzLjgwNyA1OS43NzE1QzE0NC4xNDYgNTkuOTIzOCAxNDQuNDI4IDYwLjEzODcgMTQ0LjY1IDYwLjQxNkMxNDQuODc3IDYwLjY4OTUgMTQ1LjA0NSA2MS4wMTU2IDE0NS4xNTQgNjEuMzk0NUMxNDUuMjY4IDYxLjc3MzQgMTQ1LjMyNCA2Mi4xOTE0IDE0NS4zMjQgNjIuNjQ4NFY2My4yNTJIMTQwLjQyNlY2Mi4yMzgzSDE0My45M1Y2Mi4xMjdDMTQzLjkyMiA2MS44NzMgMTQzLjg3MSA2MS42MzQ4IDE0My43NzcgNjEuNDEyMUMxNDMuNjg4IDYxLjE4OTUgMTQzLjU0OSA2MS4wMDk4IDE0My4zNjEgNjAuODczQzE0My4xNzQgNjAuNzM2MyAxNDIuOTI0IDYwLjY2OCAxNDIuNjExIDYwLjY2OEMxNDIuMzc3IDYwLjY2OCAxNDIuMTY4IDYwLjcxODggMTQxLjk4NCA2MC44MjAzQzE0MS44MDUgNjAuOTE4IDE0MS42NTQgNjEuMDYwNSAxNDEuNTMzIDYxLjI0OEMxNDEuNDEyIDYxLjQzNTUgMTQxLjMxOCA2MS42NjIxIDE0MS4yNTIgNjEuOTI3N0MxNDEuMTg5IDYyLjE4OTUgMTQxLjE1OCA2Mi40ODQ0IDE0MS4xNTggNjIuODEyNVY2My4wNDY5QzE0MS4xNTggNjMuMzI0MiAxNDEuMTk1IDYzLjU4MiAxNDEuMjcgNjMuODIwM0MxNDEuMzQ4IDY0LjA1NDcgMTQxLjQ2MSA2NC4yNTk4IDE0MS42MDkgNjQuNDM1NUMxNDEuNzU4IDY0LjYxMTMgMTQxLjkzOCA2NC43NSAxNDIuMTQ4IDY0Ljg1MTZDMTQyLjM1OSA2NC45NDkyIDE0Mi42IDY0Ljk5OCAxNDIuODY5IDY0Ljk5OEMxNDMuMjA5IDY0Ljk5OCAxNDMuNTEyIDY0LjkyOTcgMTQzLjc3NyA2NC43OTNDMTQ0LjA0MyA2NC42NTYyIDE0NC4yNzMgNjQuNDYyOSAxNDQuNDY5IDY0LjIxMjlMMTQ1LjIxMyA2NC45MzM2QzE0NS4wNzYgNjUuMTMyOCAxNDQuODk4IDY1LjMyNDIgMTQ0LjY4IDY1LjUwNzhDMTQ0LjQ2MSA2NS42ODc1IDE0NC4xOTMgNjUuODM0IDE0My44NzcgNjUuOTQ3M0MxNDMuNTY0IDY2LjA2MDUgMTQzLjIwMSA2Ni4xMTcyIDE0Mi43ODcgNjYuMTE3MlpNMTUzLjY4OCA1Ny40Mzk1VjY2SDE1Mi4yNzVWNTkuMTE1MkwxNTAuMTg0IDU5LjgyNDJWNTguNjU4MkwxNTMuNTE4IDU3LjQzOTVIMTUzLjY4OFpNMTYwLjg1MiA2NC42ODc1VjU3SDE2Mi4yN1Y2NkgxNjAuOTg2TDE2MC44NTIgNjQuNjg3NVpNMTU2LjcyNyA2Mi45MDA0VjYyLjc3NzNDMTU2LjcyNyA2Mi4yOTY5IDE1Ni43ODMgNjEuODU5NCAxNTYuODk2IDYxLjQ2NDhDMTU3LjAxIDYxLjA2NjQgMTU3LjE3NCA2MC43MjQ2IDE1Ny4zODkgNjAuNDM5NUMxNTcuNjA0IDYwLjE1MDQgMTU3Ljg2NSA1OS45Mjk3IDE1OC4xNzQgNTkuNzc3M0MxNTguNDgyIDU5LjYyMTEgMTU4LjgzIDU5LjU0MyAxNTkuMjE3IDU5LjU0M0MxNTkuNiA1OS41NDMgMTU5LjkzNiA1OS42MTcyIDE2MC4yMjUgNTkuNzY1NkMxNjAuNTE0IDU5LjkxNDEgMTYwLjc2IDYwLjEyNyAxNjAuOTYzIDYwLjQwNDNDMTYxLjE2NiA2MC42Nzc3IDE2MS4zMjggNjEuMDA1OSAxNjEuNDQ5IDYxLjM4ODdDMTYxLjU3IDYxLjc2NzYgMTYxLjY1NiA2Mi4xODk1IDE2MS43MDcgNjIuNjU0M1Y2My4wNDY5QzE2MS42NTYgNjMuNSAxNjEuNTcgNjMuOTE0MSAxNjEuNDQ5IDY0LjI4OTFDMTYxLjMyOCA2NC42NjQxIDE2MS4xNjYgNjQuOTg4MyAxNjAuOTYzIDY1LjI2MTdDMTYwLjc2IDY1LjUzNTIgMTYwLjUxMiA2NS43NDYxIDE2MC4yMTkgNjUuODk0NUMxNTkuOTMgNjYuMDQzIDE1OS41OTIgNjYuMTE3MiAxNTkuMjA1IDY2LjExNzJDMTU4LjgyMiA2Ni4xMTcyIDE1OC40NzcgNjYuMDM3MSAxNTguMTY4IDY1Ljg3N0MxNTcuODYzIDY1LjcxNjggMTU3LjYwNCA2NS40OTIyIDE1Ny4zODkgNjUuMjAzMUMxNTcuMTc0IDY0LjkxNDEgMTU3LjAxIDY0LjU3NDIgMTU2Ljg5NiA2NC4xODM2QzE1Ni43ODMgNjMuNzg5MSAxNTYuNzI3IDYzLjM2MTMgMTU2LjcyNyA2Mi45MDA0Wk0xNTguMTM5IDYyLjc3NzNWNjIuOTAwNEMxNTguMTM5IDYzLjE4OTUgMTU4LjE2NCA2My40NTkgMTU4LjIxNSA2My43MDlDMTU4LjI3IDYzLjk1OSAxNTguMzU0IDY0LjE3OTcgMTU4LjQ2NyA2NC4zNzExQzE1OC41OCA2NC41NTg2IDE1OC43MjcgNjQuNzA3IDE1OC45MDYgNjQuODE2NEMxNTkuMDkgNjQuOTIxOSAxNTkuMzA5IDY0Ljk3NDYgMTU5LjU2MiA2NC45NzQ2QzE1OS44ODMgNjQuOTc0NiAxNjAuMTQ2IDY0LjkwNDMgMTYwLjM1NCA2NC43NjM3QzE2MC41NjEgNjQuNjIzIDE2MC43MjMgNjQuNDMzNiAxNjAuODQgNjQuMTk1M0MxNjAuOTYxIDYzLjk1MzEgMTYxLjA0MyA2My42ODM2IDE2MS4wODYgNjMuMzg2N1Y2Mi4zMjYyQzE2MS4wNjIgNjIuMDk1NyAxNjEuMDE0IDYxLjg4MDkgMTYwLjkzOSA2MS42ODE2QzE2MC44NjkgNjEuNDgyNCAxNjAuNzczIDYxLjMwODYgMTYwLjY1MiA2MS4xNjAyQzE2MC41MzEgNjEuMDA3OCAxNjAuMzgxIDYwLjg5MDYgMTYwLjIwMSA2MC44MDg2QzE2MC4wMjUgNjAuNzIyNyAxNTkuODE2IDYwLjY3OTcgMTU5LjU3NCA2MC42Nzk3QzE1OS4zMTYgNjAuNjc5NyAxNTkuMDk4IDYwLjczNDQgMTU4LjkxOCA2MC44NDM4QzE1OC43MzggNjAuOTUzMSAxNTguNTkgNjEuMTAzNSAxNTguNDczIDYxLjI5NDlDMTU4LjM1OSA2MS40ODYzIDE1OC4yNzUgNjEuNzA5IDE1OC4yMjEgNjEuOTYyOUMxNTguMTY2IDYyLjIxNjggMTU4LjEzOSA2Mi40ODgzIDE1OC4xMzkgNjIuNzc3M1pNMTcwLjgwOSA2NC43Mjg1VjYxLjcwNTFDMTcwLjgwOSA2MS40Nzg1IDE3MC43NjggNjEuMjgzMiAxNzAuNjg2IDYxLjExOTFDMTcwLjYwNCA2MC45NTUxIDE3MC40NzkgNjAuODI4MSAxNzAuMzExIDYwLjczODNDMTcwLjE0NiA2MC42NDg0IDE2OS45MzkgNjAuNjAzNSAxNjkuNjg5IDYwLjYwMzVDMTY5LjQ1OSA2MC42MDM1IDE2OS4yNiA2MC42NDI2IDE2OS4wOTIgNjAuNzIwN0MxNjguOTI0IDYwLjc5ODggMTY4Ljc5MyA2MC45MDQzIDE2OC42OTkgNjEuMDM3MUMxNjguNjA1IDYxLjE2OTkgMTY4LjU1OSA2MS4zMjAzIDE2OC41NTkgNjEuNDg4M0gxNjcuMTUyQzE2Ny4xNTIgNjEuMjM4MyAxNjcuMjEzIDYwLjk5NjEgMTY3LjMzNCA2MC43NjE3QzE2Ny40NTUgNjAuNTI3MyAxNjcuNjMxIDYwLjMxODQgMTY3Ljg2MSA2MC4xMzQ4QzE2OC4wOTIgNTkuOTUxMiAxNjguMzY3IDU5LjgwNjYgMTY4LjY4OCA1OS43MDEyQzE2OS4wMDggNTkuNTk1NyAxNjkuMzY3IDU5LjU0MyAxNjkuNzY2IDU5LjU0M0MxNzAuMjQyIDU5LjU0MyAxNzAuNjY0IDU5LjYyMyAxNzEuMDMxIDU5Ljc4MzJDMTcxLjQwMiA1OS45NDM0IDE3MS42OTMgNjAuMTg1NSAxNzEuOTA0IDYwLjUwOThDMTcyLjExOSA2MC44MzAxIDE3Mi4yMjcgNjEuMjMyNCAxNzIuMjI3IDYxLjcxNjhWNjQuNTM1MkMxNzIuMjI3IDY0LjgyNDIgMTcyLjI0NiA2NS4wODQgMTcyLjI4NSA2NS4zMTQ1QzE3Mi4zMjggNjUuNTQxIDE3Mi4zODkgNjUuNzM4MyAxNzIuNDY3IDY1LjkwNjJWNjZIMTcxLjAyQzE3MC45NTMgNjUuODQ3NyAxNzAuOSA2NS42NTQzIDE3MC44NjEgNjUuNDE5OUMxNzAuODI2IDY1LjE4MTYgMTcwLjgwOSA2NC45NTEyIDE3MC44MDkgNjQuNzI4NVpNMTcxLjAxNCA2Mi4xNDQ1TDE3MS4wMjUgNjMuMDE3NkgxNzAuMDEyQzE2OS43NSA2My4wMTc2IDE2OS41MiA2My4wNDMgMTY5LjMyIDYzLjA5MzhDMTY5LjEyMSA2My4xNDA2IDE2OC45NTUgNjMuMjEwOSAxNjguODIyIDYzLjMwNDdDMTY4LjY4OSA2My4zOTg0IDE2OC41OSA2My41MTE3IDE2OC41MjMgNjMuNjQ0NUMxNjguNDU3IDYzLjc3NzMgMTY4LjQyNCA2My45Mjc3IDE2OC40MjQgNjQuMDk1N0MxNjguNDI0IDY0LjI2MzcgMTY4LjQ2MyA2NC40MTggMTY4LjU0MSA2NC41NTg2QzE2OC42MTkgNjQuNjk1MyAxNjguNzMyIDY0LjgwMjcgMTY4Ljg4MSA2NC44ODA5QzE2OS4wMzMgNjQuOTU5IDE2OS4yMTcgNjQuOTk4IDE2OS40MzIgNjQuOTk4QzE2OS43MjEgNjQuOTk4IDE2OS45NzMgNjQuOTM5NSAxNzAuMTg4IDY0LjgyMjNDMTcwLjQwNiA2NC43MDEyIDE3MC41NzggNjQuNTU0NyAxNzAuNzAzIDY0LjM4MjhDMTcwLjgyOCA2NC4yMDcgMTcwLjg5NSA2NC4wNDEgMTcwLjkwMiA2My44ODQ4TDE3MS4zNTkgNjQuNTExN0MxNzEuMzEyIDY0LjY3MTkgMTcxLjIzMiA2NC44NDM4IDE3MS4xMTkgNjUuMDI3M0MxNzEuMDA2IDY1LjIxMDkgMTcwLjg1NyA2NS4zODY3IDE3MC42NzQgNjUuNTU0N0MxNzAuNDk0IDY1LjcxODggMTcwLjI3NyA2NS44NTM1IDE3MC4wMjMgNjUuOTU5QzE2OS43NzMgNjYuMDY0NSAxNjkuNDg0IDY2LjExNzIgMTY5LjE1NiA2Ni4xMTcyQzE2OC43NDIgNjYuMTE3MiAxNjguMzczIDY2LjAzNTIgMTY4LjA0OSA2NS44NzExQzE2Ny43MjUgNjUuNzAzMSAxNjcuNDcxIDY1LjQ3ODUgMTY3LjI4NyA2NS4xOTczQzE2Ny4xMDQgNjQuOTEyMSAxNjcuMDEyIDY0LjU4OTggMTY3LjAxMiA2NC4yMzA1QzE2Ny4wMTIgNjMuODk0NSAxNjcuMDc0IDYzLjU5NzcgMTY3LjE5OSA2My4zMzk4QzE2Ny4zMjggNjMuMDc4MSAxNjcuNTE2IDYyLjg1OTQgMTY3Ljc2MiA2Mi42ODM2QzE2OC4wMTIgNjIuNTA3OCAxNjguMzE2IDYyLjM3NSAxNjguNjc2IDYyLjI4NTJDMTY5LjAzNSA2Mi4xOTE0IDE2OS40NDUgNjIuMTQ0NSAxNjkuOTA2IDYyLjE0NDVIMTcxLjAxNFpNMTc4LjAxNCA1OS42NjAySDE3OS4yOTdWNjUuODI0MkMxNzkuMjk3IDY2LjM5NDUgMTc5LjE3NiA2Ni44Nzg5IDE3OC45MzQgNjcuMjc3M0MxNzguNjkxIDY3LjY3NTggMTc4LjM1NCA2Ny45Nzg1IDE3Ny45MiA2OC4xODU1QzE3Ny40ODYgNjguMzk2NSAxNzYuOTg0IDY4LjUwMiAxNzYuNDE0IDY4LjUwMkMxNzYuMTcyIDY4LjUwMiAxNzUuOTAyIDY4LjQ2NjggMTc1LjYwNSA2OC4zOTY1QzE3NS4zMTIgNjguMzI2MiAxNzUuMDI3IDY4LjIxMjkgMTc0Ljc1IDY4LjA1NjZDMTc0LjQ3NyA2Ny45MDQzIDE3NC4yNDggNjcuNzAzMSAxNzQuMDY0IDY3LjQ1MzFMMTc0LjcyNyA2Ni42MjExQzE3NC45NTMgNjYuODkwNiAxNzUuMjAzIDY3LjA4NzkgMTc1LjQ3NyA2Ny4yMTI5QzE3NS43NSA2Ny4zMzc5IDE3Ni4wMzcgNjcuNDAwNCAxNzYuMzM4IDY3LjQwMDRDMTc2LjY2MiA2Ny40MDA0IDE3Ni45MzggNjcuMzM5OCAxNzcuMTY0IDY3LjIxODhDMTc3LjM5NSA2Ny4xMDE2IDE3Ny41NzIgNjYuOTI3NyAxNzcuNjk3IDY2LjY5NzNDMTc3LjgyMiA2Ni40NjY4IDE3Ny44ODUgNjYuMTg1NSAxNzcuODg1IDY1Ljg1MzVWNjEuMDk1N0wxNzguMDE0IDU5LjY2MDJaTTE3My43MDcgNjIuOTAwNFY2Mi43NzczQzE3My43MDcgNjIuMjk2OSAxNzMuNzY2IDYxLjg1OTQgMTczLjg4MyA2MS40NjQ4QzE3NCA2MS4wNjY0IDE3NC4xNjggNjAuNzI0NiAxNzQuMzg3IDYwLjQzOTVDMTc0LjYwNSA2MC4xNTA0IDE3NC44NzEgNTkuOTI5NyAxNzUuMTg0IDU5Ljc3NzNDMTc1LjQ5NiA1OS42MjExIDE3NS44NSA1OS41NDMgMTc2LjI0NCA1OS41NDNDMTc2LjY1NCA1OS41NDMgMTc3LjAwNCA1OS42MTcyIDE3Ny4yOTMgNTkuNzY1NkMxNzcuNTg2IDU5LjkxNDEgMTc3LjgzIDYwLjEyNyAxNzguMDI1IDYwLjQwNDNDMTc4LjIyMSA2MC42Nzc3IDE3OC4zNzMgNjEuMDA1OSAxNzguNDgyIDYxLjM4ODdDMTc4LjU5NiA2MS43Njc2IDE3OC42OCA2Mi4xODk1IDE3OC43MzQgNjIuNjU0M1Y2My4wNDY5QzE3OC42ODQgNjMuNSAxNzguNTk4IDYzLjkxNDEgMTc4LjQ3NyA2NC4yODkxQzE3OC4zNTUgNjQuNjY0MSAxNzguMTk1IDY0Ljk4ODMgMTc3Ljk5NiA2NS4yNjE3QzE3Ny43OTcgNjUuNTM1MiAxNzcuNTUxIDY1Ljc0NjEgMTc3LjI1OCA2NS44OTQ1QzE3Ni45NjkgNjYuMDQzIDE3Ni42MjcgNjYuMTE3MiAxNzYuMjMyIDY2LjExNzJDMTc1Ljg0NiA2Ni4xMTcyIDE3NS40OTYgNjYuMDM3MSAxNzUuMTg0IDY1Ljg3N0MxNzQuODc1IDY1LjcxNjggMTc0LjYwOSA2NS40OTIyIDE3NC4zODcgNjUuMjAzMUMxNzQuMTY4IDY0LjkxNDEgMTc0IDY0LjU3NDIgMTczLjg4MyA2NC4xODM2QzE3My43NjYgNjMuNzg5MSAxNzMuNzA3IDYzLjM2MTMgMTczLjcwNyA2Mi45MDA0Wk0xNzUuMTE5IDYyLjc3NzNWNjIuOTAwNEMxNzUuMTE5IDYzLjE4OTUgMTc1LjE0NiA2My40NTkgMTc1LjIwMSA2My43MDlDMTc1LjI2IDYzLjk1OSAxNzUuMzQ4IDY0LjE3OTcgMTc1LjQ2NSA2NC4zNzExQzE3NS41ODYgNjQuNTU4NiAxNzUuNzM4IDY0LjcwNyAxNzUuOTIyIDY0LjgxNjRDMTc2LjEwOSA2NC45MjE5IDE3Ni4zMyA2NC45NzQ2IDE3Ni41ODQgNjQuOTc0NkMxNzYuOTE2IDY0Ljk3NDYgMTc3LjE4OCA2NC45MDQzIDE3Ny4zOTggNjQuNzYzN0MxNzcuNjEzIDY0LjYyMyAxNzcuNzc3IDY0LjQzMzYgMTc3Ljg5MSA2NC4xOTUzQzE3OC4wMDggNjMuOTUzMSAxNzguMDkgNjMuNjgzNiAxNzguMTM3IDYzLjM4NjdWNjIuMzI2MkMxNzguMTEzIDYyLjA5NTcgMTc4LjA2NCA2MS44ODA5IDE3Ny45OSA2MS42ODE2QzE3Ny45MiA2MS40ODI0IDE3Ny44MjQgNjEuMzA4NiAxNzcuNzAzIDYxLjE2MDJDMTc3LjU4MiA2MS4wMDc4IDE3Ny40MyA2MC44OTA2IDE3Ny4yNDYgNjAuODA4NkMxNzcuMDYyIDYwLjcyMjcgMTc2Ljg0NiA2MC42Nzk3IDE3Ni41OTYgNjAuNjc5N0MxNzYuMzQyIDYwLjY3OTcgMTc2LjEyMSA2MC43MzQ0IDE3NS45MzQgNjAuODQzOEMxNzUuNzQ2IDYwLjk1MzEgMTc1LjU5MiA2MS4xMDM1IDE3NS40NzEgNjEuMjk0OUMxNzUuMzU0IDYxLjQ4NjMgMTc1LjI2NiA2MS43MDkgMTc1LjIwNyA2MS45NjI5QzE3NS4xNDggNjIuMjE2OCAxNzUuMTE5IDYyLjQ4ODMgMTc1LjExOSA2Mi43NzczWk0xODAuNzQyIDYyLjkwMDRWNjIuNzY1NkMxODAuNzQyIDYyLjMwODYgMTgwLjgwOSA2MS44ODQ4IDE4MC45NDEgNjEuNDk0MUMxODEuMDc0IDYxLjA5OTYgMTgxLjI2NiA2MC43NTc4IDE4MS41MTYgNjAuNDY4OEMxODEuNzcgNjAuMTc1OCAxODIuMDc4IDU5Ljk0OTIgMTgyLjQ0MSA1OS43ODkxQzE4Mi44MDkgNTkuNjI1IDE4My4yMjMgNTkuNTQzIDE4My42ODQgNTkuNTQzQzE4NC4xNDggNTkuNTQzIDE4NC41NjIgNTkuNjI1IDE4NC45MjYgNTkuNzg5MUMxODUuMjkzIDU5Ljk0OTIgMTg1LjYwNCA2MC4xNzU4IDE4NS44NTcgNjAuNDY4OEMxODYuMTExIDYwLjc1NzggMTg2LjMwNSA2MS4wOTk2IDE4Ni40MzggNjEuNDk0MUMxODYuNTcgNjEuODg0OCAxODYuNjM3IDYyLjMwODYgMTg2LjYzNyA2Mi43NjU2VjYyLjkwMDRDMTg2LjYzNyA2My4zNTc0IDE4Ni41NyA2My43ODEyIDE4Ni40MzggNjQuMTcxOUMxODYuMzA1IDY0LjU2MjUgMTg2LjExMSA2NC45MDQzIDE4NS44NTcgNjUuMTk3M0MxODUuNjA0IDY1LjQ4NjMgMTg1LjI5NSA2NS43MTI5IDE4NC45MzIgNjUuODc3QzE4NC41NjggNjYuMDM3MSAxODQuMTU2IDY2LjExNzIgMTgzLjY5NSA2Ni4xMTcyQzE4My4yMyA2Ni4xMTcyIDE4Mi44MTQgNjYuMDM3MSAxODIuNDQ3IDY1Ljg3N0MxODIuMDg0IDY1LjcxMjkgMTgxLjc3NSA2NS40ODYzIDE4MS41MjEgNjUuMTk3M0MxODEuMjY4IDY0LjkwNDMgMTgxLjA3NCA2NC41NjI1IDE4MC45NDEgNjQuMTcxOUMxODAuODA5IDYzLjc4MTIgMTgwLjc0MiA2My4zNTc0IDE4MC43NDIgNjIuOTAwNFpNMTgyLjE1NCA2Mi43NjU2VjYyLjkwMDRDMTgyLjE1NCA2My4xODU1IDE4Mi4xODQgNjMuNDU1MSAxODIuMjQyIDYzLjcwOUMxODIuMzAxIDYzLjk2MjkgMTgyLjM5MyA2NC4xODU1IDE4Mi41MTggNjQuMzc3QzE4Mi42NDMgNjQuNTY4NCAxODIuODAzIDY0LjcxODggMTgyLjk5OCA2NC44MjgxQzE4My4xOTMgNjQuOTM3NSAxODMuNDI2IDY0Ljk5MjIgMTgzLjY5NSA2NC45OTIyQzE4My45NTcgNjQuOTkyMiAxODQuMTg0IDY0LjkzNzUgMTg0LjM3NSA2NC44MjgxQzE4NC41NyA2NC43MTg4IDE4NC43MyA2NC41Njg0IDE4NC44NTUgNjQuMzc3QzE4NC45OCA2NC4xODU1IDE4NS4wNzIgNjMuOTYyOSAxODUuMTMxIDYzLjcwOUMxODUuMTkzIDYzLjQ1NTEgMTg1LjIyNSA2My4xODU1IDE4NS4yMjUgNjIuOTAwNFY2Mi43NjU2QzE4NS4yMjUgNjIuNDg0NCAxODUuMTkzIDYyLjIxODggMTg1LjEzMSA2MS45Njg4QzE4NS4wNzIgNjEuNzE0OCAxODQuOTc5IDYxLjQ5MDIgMTg0Ljg1IDYxLjI5NDlDMTg0LjcyNSA2MS4wOTk2IDE4NC41NjQgNjAuOTQ3MyAxODQuMzY5IDYwLjgzNzlDMTg0LjE3OCA2MC43MjQ2IDE4My45NDkgNjAuNjY4IDE4My42ODQgNjAuNjY4QzE4My40MTggNjAuNjY4IDE4My4xODggNjAuNzI0NiAxODIuOTkyIDYwLjgzNzlDMTgyLjgwMSA2MC45NDczIDE4Mi42NDMgNjEuMDk5NiAxODIuNTE4IDYxLjI5NDlDMTgyLjM5MyA2MS40OTAyIDE4Mi4zMDEgNjEuNzE0OCAxODIuMjQyIDYxLjk2ODhDMTgyLjE4NCA2Mi4yMTg4IDE4Mi4xNTQgNjIuNDg0NCAxODIuMTU0IDYyLjc2NTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjM4Ii8+CjxwYXRoIGQ9Ik0yODMuNTc0IDYzLjEyNVY2OEgyNTguNzkzVjYzLjgxMDVMMjcwLjgyOCA1MC42ODM2QzI3Mi4xNDggNDkuMTk0IDI3My4xODkgNDcuOTA3NiAyNzMuOTUxIDQ2LjgyNDJDMjc0LjcxMyA0NS43NDA5IDI3NS4yNDYgNDQuNzY3NiAyNzUuNTUxIDQzLjkwNDNDMjc1Ljg3MiA0My4wMjQxIDI3Ni4wMzMgNDIuMTY5MyAyNzYuMDMzIDQxLjMzOThDMjc2LjAzMyA0MC4xNzE5IDI3NS44MTMgMzkuMTQ3OCAyNzUuMzczIDM4LjI2NzZDMjc0Ljk1IDM3LjM3MDQgMjc0LjMyNCAzNi42NjggMjczLjQ5NCAzNi4xNjAyQzI3Mi42NjUgMzUuNjM1NCAyNzEuNjU4IDM1LjM3MyAyNzAuNDczIDM1LjM3M0MyNjkuMTAyIDM1LjM3MyAyNjcuOTUxIDM1LjY2OTMgMjY3LjAyIDM2LjI2MTdDMjY2LjA4OSAzNi44NTQyIDI2NS4zODYgMzcuNjc1MSAyNjQuOTEyIDM4LjcyNDZDMjY0LjQzOCAzOS43NTcyIDI2NC4yMDEgNDAuOTQyMSAyNjQuMjAxIDQyLjI3OTNIMjU4LjA4MkMyNTguMDgyIDQwLjEyOTYgMjU4LjU3MyAzOC4xNjYgMjU5LjU1NSAzNi4zODg3QzI2MC41MzYgMzQuNTk0NCAyNjEuOTU4IDMzLjE3MjUgMjYzLjgyIDMyLjEyM0MyNjUuNjgyIDMxLjA1NjYgMjY3LjkyNSAzMC41MjM0IDI3MC41NDkgMzAuNTIzNEMyNzMuMDIgMzAuNTIzNCAyNzUuMTE5IDMwLjkzODIgMjc2Ljg0NiAzMS43Njc2QzI3OC41NzIgMzIuNTk3IDI3OS44ODQgMzMuNzczNCAyODAuNzgxIDM1LjI5NjlDMjgxLjY5NSAzNi44MjAzIDI4Mi4xNTIgMzguNjIzIDI4Mi4xNTIgNDAuNzA1MUMyODIuMTUyIDQxLjg1NjEgMjgxLjk2NiA0Mi45OTg3IDI4MS41OTQgNDQuMTMyOEMyODEuMjIxIDQ1LjI2NjkgMjgwLjY4OCA0Ni40MDEgMjc5Ljk5NCA0Ny41MzUyQzI3OS4zMTcgNDguNjUyMyAyNzguNTEzIDQ5Ljc3OCAyNzcuNTgyIDUwLjkxMjFDMjc2LjY1MSA1Mi4wMjkzIDI3NS42MjcgNTMuMTYzNCAyNzQuNTEgNTQuMzE0NUwyNjYuNTEyIDYzLjEyNUgyODMuNTc0Wk0zMTIuMjE5IDYzLjEyNVY2OEgyODcuNDM4VjYzLjgxMDVMMjk5LjQ3MyA1MC42ODM2QzMwMC43OTMgNDkuMTk0IDMwMS44MzQgNDcuOTA3NiAzMDIuNTk2IDQ2LjgyNDJDMzAzLjM1OCA0NS43NDA5IDMwMy44OTEgNDQuNzY3NiAzMDQuMTk1IDQzLjkwNDNDMzA0LjUxNyA0My4wMjQxIDMwNC42NzggNDIuMTY5MyAzMDQuNjc4IDQxLjMzOThDMzA0LjY3OCA0MC4xNzE5IDMwNC40NTggMzkuMTQ3OCAzMDQuMDE4IDM4LjI2NzZDMzAzLjU5NSAzNy4zNzA0IDMwMi45NjggMzYuNjY4IDMwMi4xMzkgMzYuMTYwMkMzMDEuMzA5IDM1LjYzNTQgMzAwLjMwMiAzNS4zNzMgMjk5LjExNyAzNS4zNzNDMjk3Ljc0NiAzNS4zNzMgMjk2LjU5NSAzNS42NjkzIDI5NS42NjQgMzYuMjYxN0MyOTQuNzMzIDM2Ljg1NDIgMjk0LjAzMSAzNy42NzUxIDI5My41NTcgMzguNzI0NkMyOTMuMDgzIDM5Ljc1NzIgMjkyLjg0NiA0MC45NDIxIDI5Mi44NDYgNDIuMjc5M0gyODYuNzI3QzI4Ni43MjcgNDAuMTI5NiAyODcuMjE4IDM4LjE2NiAyODguMTk5IDM2LjM4ODdDMjg5LjE4MSAzNC41OTQ0IDI5MC42MDMgMzMuMTcyNSAyOTIuNDY1IDMyLjEyM0MyOTQuMzI3IDMxLjA1NjYgMjk2LjU3IDMwLjUyMzQgMjk5LjE5NCAzMC41MjM0QzMwMS42NjUgMzAuNTIzNCAzMDMuNzY0IDMwLjkzODIgMzA1LjQ5IDMxLjc2NzZDMzA3LjIxNyAzMi41OTcgMzA4LjUyOSAzMy43NzM0IDMwOS40MjYgMzUuMjk2OUMzMTAuMzQgMzYuODIwMyAzMTAuNzk3IDM4LjYyMyAzMTAuNzk3IDQwLjcwNTFDMzEwLjc5NyA0MS44NTYxIDMxMC42MTEgNDIuOTk4NyAzMTAuMjM4IDQ0LjEzMjhDMzA5Ljg2NiA0NS4yNjY5IDMwOS4zMzMgNDYuNDAxIDMwOC42MzkgNDcuNTM1MkMzMDcuOTYyIDQ4LjY1MjMgMzA3LjE1OCA0OS43NzggMzA2LjIyNyA1MC45MTIxQzMwNS4yOTYgNTIuMDI5MyAzMDQuMjcyIDUzLjE2MzQgMzAzLjE1NCA1NC4zMTQ1TDI5NS4xNTYgNjMuMTI1SDMxMi4yMTlaTTMxNi41NjUgMzcuMzAyN0MzMTYuNTY1IDM2LjA2NzEgMzE2Ljg2OSAzNC45MzI5IDMxNy40NzkgMzMuOTAwNEMzMTguMDg4IDMyLjg2NzggMzE4LjkwMSAzMi4wNDY5IDMxOS45MTYgMzEuNDM3NUMzMjAuOTQ5IDMwLjgxMTIgMzIyLjA2NiAzMC40OTggMzIzLjI2OCAzMC40OThDMzI0LjQ4NyAzMC40OTggMzI1LjU5NSAzMC44MTEyIDMyNi41OTQgMzEuNDM3NUMzMjcuNTkzIDMyLjA0NjkgMzI4LjM4OCAzMi44Njc4IDMyOC45ODEgMzMuOTAwNEMzMjkuNTkgMzQuOTMyOSAzMjkuODk1IDM2LjA2NzEgMzI5Ljg5NSAzNy4zMDI3QzMyOS44OTUgMzguNTM4NCAzMjkuNTkgMzkuNjcyNSAzMjguOTgxIDQwLjcwNTFDMzI4LjM4OCA0MS43MjA3IDMyNy41OTMgNDIuNTI0NyAzMjYuNTk0IDQzLjExNzJDMzI1LjU5NSA0My43MDk2IDMyNC40ODcgNDQuMDA1OSAzMjMuMjY4IDQ0LjAwNTlDMzIyLjA2NiA0NC4wMDU5IDMyMC45NDkgNDMuNzA5NiAzMTkuOTE2IDQzLjExNzJDMzE4LjkwMSA0Mi41MjQ3IDMxOC4wODggNDEuNzIwNyAzMTcuNDc5IDQwLjcwNTFDMzE2Ljg2OSAzOS42NzI1IDMxNi41NjUgMzguNTM4NCAzMTYuNTY1IDM3LjMwMjdaTTMxOS45OTMgMzcuMzAyN0MzMTkuOTkzIDM4LjIxNjggMzIwLjMxNCAzOC45ODcgMzIwLjk1NyAzOS42MTMzQzMyMS42MDEgNDAuMjIyNyAzMjIuMzcxIDQwLjUyNzMgMzIzLjI2OCA0MC41MjczQzMyNC4xNjUgNDAuNTI3MyAzMjQuOTE4IDQwLjIyMjcgMzI1LjUyOCAzOS42MTMzQzMyNi4xMzcgMzkuMDAzOSAzMjYuNDQyIDM4LjIzMzcgMzI2LjQ0MiAzNy4zMDI3QzMyNi40NDIgMzYuMzU0OCAzMjYuMTM3IDM1LjU2NzcgMzI1LjUyOCAzNC45NDE0QzMyNC45MTggMzQuMzE1MSAzMjQuMTY1IDM0LjAwMiAzMjMuMjY4IDM0LjAwMkMzMjIuMzcxIDM0LjAwMiAzMjEuNjAxIDM0LjMxNTEgMzIwLjk1NyAzNC45NDE0QzMyMC4zMTQgMzUuNTY3NyAzMTkuOTkzIDM2LjM1NDggMzE5Ljk5MyAzNy4zMDI3Wk0zNTcuODc5IDU1Ljk2NDhIMzY0LjIyN0MzNjQuMDI0IDU4LjM4NTQgMzYzLjM0NyA2MC41NDM2IDM2Mi4xOTYgNjIuNDM5NUMzNjEuMDQ1IDY0LjMxODQgMzU5LjQyOCA2NS43OTk1IDM1Ny4zNDYgNjYuODgyOEMzNTUuMjY0IDY3Ljk2NjEgMzUyLjczNCA2OC41MDc4IDM0OS43NTQgNjguNTA3OEMzNDcuNDY5IDY4LjUwNzggMzQ1LjQxMyA2OC4xMDE2IDM0My41ODQgNjcuMjg5MUMzNDEuNzU2IDY2LjQ1OTYgMzQwLjE5MSA2NS4yOTE3IDMzOC44ODcgNjMuNzg1MkMzMzcuNTg0IDYyLjI2MTcgMzM2LjU4NSA2MC40MjUxIDMzNS44OTEgNTguMjc1NEMzMzUuMjE0IDU2LjEyNTcgMzM0Ljg3NSA1My43MjIgMzM0Ljg3NSA1MS4wNjQ1VjQ3Ljk5MjJDMzM0Ljg3NSA0NS4zMzQ2IDMzNS4yMjIgNDIuOTMxIDMzNS45MTYgNDAuNzgxMkMzMzYuNjI3IDM4LjYzMTUgMzM3LjY0MyAzNi43OTQ5IDMzOC45NjMgMzUuMjcxNUMzNDAuMjg0IDMzLjczMTEgMzQxLjg2NiAzMi41NTQ3IDM0My43MTEgMzEuNzQyMkMzNDUuNTczIDMwLjkyOTcgMzQ3LjY2NCAzMC41MjM0IDM0OS45ODMgMzAuNTIzNEMzNTIuOTI4IDMwLjUyMzQgMzU1LjQxNiAzMS4wNjUxIDM1Ny40NDggMzIuMTQ4NEMzNTkuNDc5IDMzLjIzMTggMzYxLjA1MyAzNC43Mjk4IDM2Mi4xNyAzNi42NDI2QzM2My4zMDUgMzguNTU1MyAzNjMuOTk5IDQwLjc0NzQgMzY0LjI1MiA0My4yMTg4SDM1Ny45MDVDMzU3LjczNSA0MS42Mjc2IDM1Ny4zNjMgNDAuMjY1IDM1Ni43ODggMzkuMTMwOUMzNTYuMjI5IDM3Ljk5NjcgMzU1LjQgMzcuMTMzNSAzNTQuMjk5IDM2LjU0MUMzNTMuMTk5IDM1LjkzMTYgMzUxLjc2IDM1LjYyNyAzNDkuOTgzIDM1LjYyN0MzNDguNTI3IDM1LjYyNyAzNDcuMjU4IDM1Ljg5NzggMzQ2LjE3NCAzNi40Mzk1QzM0NS4wOTEgMzYuOTgxMSAzNDQuMTg1IDM3Ljc3NjcgMzQzLjQ1NyAzOC44MjYyQzM0Mi43MyAzOS44NzU3IDM0Mi4xOCA0MS4xNzA2IDM0MS44MDcgNDIuNzEwOUMzNDEuNDUyIDQ0LjIzNDQgMzQxLjI3NCA0NS45Nzc5IDM0MS4yNzQgNDcuOTQxNFY1MS4wNjQ1QzM0MS4yNzQgNTIuOTI2NCAzNDEuNDM1IDU0LjYxOTEgMzQxLjc1NiA1Ni4xNDI2QzM0Mi4wOTUgNTcuNjQ5MSAzNDIuNjAzIDU4Ljk0NCAzNDMuMjggNjAuMDI3M0MzNDMuOTc0IDYxLjExMDcgMzQ0Ljg1NCA2MS45NDg2IDM0NS45MiA2Mi41NDFDMzQ2Ljk4NyA2My4xMzM1IDM0OC4yNjUgNjMuNDI5NyAzNDkuNzU0IDYzLjQyOTdDMzUxLjU2NiA2My40Mjk3IDM1My4wMyA2My4xNDE5IDM1NC4xNDcgNjIuNTY2NEMzNTUuMjgxIDYxLjk5MDkgMzU2LjEzNiA2MS4xNTMgMzU2LjcxMSA2MC4wNTI3QzM1Ny4zMDQgNTguOTM1NSAzNTcuNjkzIDU3LjU3MjkgMzU3Ljg3OSA1NS45NjQ4WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC44NyIvPgo8L2c+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfMTI0Nl80NDQ0NyIgeD0iMCIgeT0iMCIgd2lkdGg9IjM5OSIgaGVpZ2h0PSIxMDgiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldCBkeT0iNCIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSI0Ii8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4wNCAwIi8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0iZWZmZWN0MV9kcm9wU2hhZG93XzEyNDZfNDQ0NDciLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3dfMTI0Nl80NDQ0NyIgcmVzdWx0PSJzaGFwZSIvPgo8L2ZpbHRlcj4KPC9kZWZzPgo8L3N2Zz4K", "description": "Designed to display single value of the selected attribute or timeseries data. Widget styles are customizable.", "descriptor": { "type": "latest", @@ -259,10 +259,10 @@ "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n horizontal: true,\n previewWidth: '420px',\n previewHeight: '130px'\n };\n};\n\nself.onDestroy = function() {\n};\n", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n horizontal: true,\n previewWidth: '420px',\n previewHeight: '130px',\n absoluteHeader: true\n };\n};\n\nself.onDestroy = function() {\n};\n", "settingsSchema": "", "dataKeySettingsSchema": "", - "settingsDirective": "", + "settingsDirective": "tb-value-card-widget-settings", "hasBasicMode": true, "basicModeDirective": "tb-value-card-basic-config", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Horizontal value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}" diff --git a/ui-ngx/src/app/core/services/dashboard-utils.service.ts b/ui-ngx/src/app/core/services/dashboard-utils.service.ts index 8355ce2f61..a2a33b73a1 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -348,10 +348,8 @@ export class DashboardUtilsService { private convertDatasourcesFromWidgetType(widgetTypeDescriptor: WidgetTypeDescriptor, config: WidgetConfig, datasources?: Datasource[]): Datasource[] { const newDatasources: Datasource[] = []; - if (datasources) { - datasources.forEach(datasource => { - newDatasources.push(this.convertDatasourceFromWidgetType(widgetTypeDescriptor, config, datasource)); - }); + if (datasources?.length) { + newDatasources.push(this.convertDatasourceFromWidgetType(widgetTypeDescriptor, config, datasources[0])); } return newDatasources; } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss index 6c3b90da84..3856547508 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss @@ -21,9 +21,9 @@ position: relative; } @media #{$mat-gt-xs} { - width: 1200px; + width: 900px; .mat-mdc-dialog-content { - height: 600px; + height: 900px; } } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 39940546c2..cead69ab46 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -1177,6 +1177,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC Widget>(AddWidgetDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + maxWidth: '95vw', data: { dashboard: this.dashboard, aliasController: this.dashboardCtx.aliasController, diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss index a82f9c2f8b..e86f828111 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss @@ -45,6 +45,8 @@ } .preview { + width: 100%; + height: 100%; max-width: 100%; max-height: 100%; object-fit: contain; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html index b5808a8c96..51bb854826 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html @@ -55,7 +55,7 @@
-
+
{{ 'widgets.value-card.icon' | translate }} @@ -87,18 +87,38 @@
-
-
- - {{ 'widgets.value-card.date' | translate }} - -
- - - - - +
+ + {{ 'widgets.value-card.date' | translate }} + +
+ + + + + +
+
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
widget-config.show-card-buttons
+ + {{ 'fullscreen.fullscreen' | translate }} + +
+
+
{{ 'widget-config.card-border-radius' | translate }}
+ + +
+ + diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts index 762b26ac42..f00ec8e2e6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts @@ -74,6 +74,16 @@ export class ValueCardBasicConfigComponent extends BasicWidgetConfigComponent { datePreviewFn = this._datePreviewFn.bind(this); + get dateEnabled(): boolean { + const layout: ValueCardLayout = this.valueCardWidgetConfigForm.get('layout').value; + return ![ValueCardLayout.vertical, ValueCardLayout.simplified].includes(layout); + } + + get iconEnabled(): boolean { + const layout: ValueCardLayout = this.valueCardWidgetConfigForm.get('layout').value; + return layout !== ValueCardLayout.simplified; + } + constructor(protected store: Store, protected widgetConfigComponent: WidgetConfigComponent, private cd: ChangeDetectorRef, diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html index b603f98a4d..22c4c2aace 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html @@ -22,7 +22,7 @@ {{ 'datakey.latest' | translate }} - + @@ -44,7 +44,7 @@ matTooltipPosition="above">timeline
-
+
@@ -139,7 +139,7 @@ - +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss index 41a003985e..fabd561a97 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss @@ -38,7 +38,16 @@ } .tb-source-field { - width: 140px; + width: 120px; + min-width: 120px; + } + + .tb-key-field { + flex: 1 1 60%; + } + + .tb-label-field { + flex: 1 1 40%; } .tb-color-field, .tb-units-field, .tb-decimals-field { @@ -50,9 +59,11 @@ .tb-units-field { width: 80px; + min-width: 80px; } .tb-color-field, .tb-decimals-field { width: 60px; + min-width: 60px; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html index 03e1b4761b..a2cddcde90 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html @@ -20,8 +20,8 @@
datakey.source
-
datakey.key
-
datakey.label
+
datakey.key
+
datakey.label
datakey.color
widget-config.units-short
widget-config.decimals-short
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss index 6ce13d7adc..7d33fd50a4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss @@ -15,15 +15,25 @@ */ .tb-form-table-header-cell { &.tb-source-header { - width: 140px; + width: 120px; + min-width: 120px; + } + &.tb-key-header { + flex: 1 1 60%; + } + &.tb-label-header { + flex: 1 1 40%; } &.tb-units-header { width: 80px; + min-width: 80px; } &.tb-color-header, &.tb-decimals-header { width: 60px; + min-width: 60px; } &.tb-actions-header { width: 114px; + min-width: 114px; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html index 0b4c774393..e024af20ee 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html @@ -65,7 +65,7 @@ {{key.label}}
:
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss index 7d01f41cb5..415c69ec53 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss @@ -65,9 +65,11 @@ font-weight: normal; font-size: 14px; line-height: 20px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + &.tb-chip-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } .mat-icon.tb-datakey-icon { margin-right: 4px; margin-left: 4px; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts index cc482bfd4e..bf2144cdfd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts @@ -44,6 +44,7 @@ import { widgetSettingsComponentsMap } from '@home/components/widget/lib/setting import { Dashboard } from '@shared/models/dashboard.models'; import { WidgetService } from '@core/http/widget.service'; import { IAliasController } from '@core/api/widget-api.models'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; @Component({ selector: 'tb-widget-settings', @@ -73,6 +74,9 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On @Input() widget: Widget; + @Input() + widgetConfig: WidgetConfigComponentData; + private settingsDirective: string; definedDirectiveError: string; @@ -126,6 +130,11 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On this.definedSettingsComponent.aliasController = this.aliasController; } } + if (propName === 'widgetConfig') { + if (this.definedSettingsComponent) { + this.definedSettingsComponent.widgetConfig = this.widgetConfig; + } + } } } } @@ -214,6 +223,7 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On this.definedSettingsComponent.aliasController = this.aliasController; this.definedSettingsComponent.dashboard = this.dashboard; this.definedSettingsComponent.widget = this.widget; + this.definedSettingsComponent.widgetConfig = this.widgetConfig; this.definedSettingsComponent.functionScopeVariables = this.widgetService.getWidgetScopeVariables(); this.changeSubscription = this.definedSettingsComponent.settingsChanged.subscribe((settings) => { this.updateModel(settings); diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts index 34f2ac464b..0fa061de72 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts @@ -276,6 +276,14 @@ export enum BackgroundType { color = 'color' } +export const backgroundTypeTranslations = new Map( + [ + [BackgroundType.image, 'widgets.background.background-type-image'], + [BackgroundType.imageUrl, 'widgets.background.background-type-image-url'], + [BackgroundType.color, 'widgets.background.background-type-color'] + ] +); + export interface OverlaySettings { enabled: boolean; color: string; @@ -313,11 +321,13 @@ export const backgroundStyle = (background: BackgroundSettings): ComponentStyle }; } else { const imageUrl = background.type === BackgroundType.image ? background.imageBase64 : background.imageUrl; - return { - background: `url(${imageUrl}) no-repeat`, - backgroundSize: 'cover', - backgroundPosition: '50% 50%' - }; + if (imageUrl) { + return { + background: `url(${imageUrl}) no-repeat 50% 50% / cover` + }; + } else { + return {}; + } } }; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html new file mode 100644 index 0000000000..423c727a8d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html @@ -0,0 +1,89 @@ + + +
+
widgets.value-card.value-card-style
+ + + {{ valueCardLayoutTranslationMap.get(layout) | translate }} + + +
+ + {{ 'widgets.value-card.label' | translate }} + +
+ + + + +
+
+
+ + {{ 'widgets.value-card.icon' | translate }} + +
+ + + + + + + + +
+
+
+
widgets.value-card.value
+
+ + + + +
+
+
+ + {{ 'widgets.value-card.date' | translate }} + +
+ + + + + +
+
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts new file mode 100644 index 0000000000..6f76546ac1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts @@ -0,0 +1,200 @@ +/// +/// 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. +/// + +import { Component, Injector } from '@angular/core'; +import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { + valueCardDefaultSettings, + ValueCardLayout, valueCardLayoutImages, + valueCardLayouts, valueCardLayoutTranslations +} from '@home/components/widget/lib/cards/value-card-widget.models'; +import { formatValue, isDefinedAndNotNull } from '@core/utils'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { + DateFormatProcessor, + DateFormatSettings, + getLabel +} from '@home/components/widget/config/widget-settings.models'; + +@Component({ + selector: 'tb-value-card-widget-settings', + templateUrl: './value-card-widget-settings.component.html', + styleUrls: [] +}) +export class ValueCardWidgetSettingsComponent extends WidgetSettingsComponent { + + valueCardLayouts: ValueCardLayout[] = []; + + valueCardLayoutTranslationMap = valueCardLayoutTranslations; + valueCardLayoutImageMap = valueCardLayoutImages; + + horizontal = false; + + valueCardWidgetSettingsForm: UntypedFormGroup; + + valuePreviewFn = this._valuePreviewFn.bind(this); + + datePreviewFn = this._datePreviewFn.bind(this); + + + get label(): string { + return getLabel(this.widgetConfig.config.datasources); + } + + get dateEnabled(): boolean { + const layout: ValueCardLayout = this.valueCardWidgetSettingsForm.get('layout').value; + return ![ValueCardLayout.vertical, ValueCardLayout.simplified].includes(layout); + } + + get iconEnabled(): boolean { + const layout: ValueCardLayout = this.valueCardWidgetSettingsForm.get('layout').value; + return layout !== ValueCardLayout.simplified; + } + + constructor(protected store: Store, + private $injector: Injector, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.valueCardWidgetSettingsForm; + } + + protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) { + const params = widgetConfig.typeParameters as any; + this.horizontal = isDefinedAndNotNull(params.horizontal) ? params.horizontal : false; + this.valueCardLayouts = valueCardLayouts(this.horizontal); + } + + protected defaultSettings(): WidgetSettings { + return valueCardDefaultSettings(this.horizontal); + } + + protected onSettingsSet(settings: WidgetSettings) { + this.valueCardWidgetSettingsForm = this.fb.group({ + layout: [settings.layout, []], + + showLabel: [settings.showLabel, []], + labelFont: [settings.labelFont, []], + labelColor: [settings.labelColor, []], + + showIcon: [settings.showIcon, []], + iconSize: [settings.iconSize, [Validators.min(0)]], + iconSizeUnit: [settings.iconSizeUnit, []], + icon: [settings.icon, []], + iconColor: [settings.iconColor, []], + + valueFont: [settings.valueFont, []], + valueColor: [settings.valueColor, []], + + showDate: [settings.showDate, []], + dateFormat: [settings.dateFormat, []], + dateFont: [settings.dateFont, []], + dateColor: [settings.dateColor, []], + + background: [settings.background, []] + }); + } + + protected validatorTriggers(): string[] { + return ['layout', 'showLabel', 'showIcon', 'showDate']; + } + + protected updateValidators(emitEvent: boolean) { + const layout: ValueCardLayout = this.valueCardWidgetSettingsForm.get('layout').value; + const showLabel: boolean = this.valueCardWidgetSettingsForm.get('showLabel').value; + const showIcon: boolean = this.valueCardWidgetSettingsForm.get('showIcon').value; + const showDate: boolean = this.valueCardWidgetSettingsForm.get('showDate').value; + + const dateEnabled = ![ValueCardLayout.vertical, ValueCardLayout.simplified].includes(layout); + const iconEnabled = layout !== ValueCardLayout.simplified; + + if (showLabel) { + this.valueCardWidgetSettingsForm.get('labelFont').enable(); + this.valueCardWidgetSettingsForm.get('labelColor').enable(); + } else { + this.valueCardWidgetSettingsForm.get('labelFont').disable(); + this.valueCardWidgetSettingsForm.get('labelColor').disable(); + } + + if (iconEnabled) { + this.valueCardWidgetSettingsForm.get('showIcon').enable({emitEvent: false}); + if (showIcon) { + this.valueCardWidgetSettingsForm.get('iconSize').enable(); + this.valueCardWidgetSettingsForm.get('iconSizeUnit').enable(); + this.valueCardWidgetSettingsForm.get('icon').enable(); + this.valueCardWidgetSettingsForm.get('iconColor').enable(); + } else { + this.valueCardWidgetSettingsForm.get('iconSize').disable(); + this.valueCardWidgetSettingsForm.get('iconSizeUnit').disable(); + this.valueCardWidgetSettingsForm.get('icon').disable(); + this.valueCardWidgetSettingsForm.get('iconColor').disable(); + } + } else { + this.valueCardWidgetSettingsForm.get('showIcon').disable({emitEvent: false}); + this.valueCardWidgetSettingsForm.get('iconSize').disable(); + this.valueCardWidgetSettingsForm.get('iconSizeUnit').disable(); + this.valueCardWidgetSettingsForm.get('icon').disable(); + this.valueCardWidgetSettingsForm.get('iconColor').disable(); + } + + if (dateEnabled) { + this.valueCardWidgetSettingsForm.get('showDate').enable({emitEvent: false}); + if (showDate) { + this.valueCardWidgetSettingsForm.get('dateFormat').enable(); + this.valueCardWidgetSettingsForm.get('dateFont').enable(); + this.valueCardWidgetSettingsForm.get('dateColor').enable(); + } else { + this.valueCardWidgetSettingsForm.get('dateFormat').disable(); + this.valueCardWidgetSettingsForm.get('dateFont').disable(); + this.valueCardWidgetSettingsForm.get('dateColor').disable(); + } + } else { + this.valueCardWidgetSettingsForm.get('showDate').disable({emitEvent: false}); + this.valueCardWidgetSettingsForm.get('dateFormat').disable(); + this.valueCardWidgetSettingsForm.get('dateFont').disable(); + this.valueCardWidgetSettingsForm.get('dateColor').disable(); + } + this.valueCardWidgetSettingsForm.get('showIcon').updateValueAndValidity({emitEvent: false}); + this.valueCardWidgetSettingsForm.get('showDate').updateValueAndValidity({emitEvent: false}); + this.valueCardWidgetSettingsForm.get('labelFont').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('labelColor').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('iconSize').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('iconSizeUnit').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('icon').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('iconColor').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('dateFormat').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('dateFont').updateValueAndValidity({emitEvent}); + this.valueCardWidgetSettingsForm.get('dateColor').updateValueAndValidity({emitEvent}); + } + + private _valuePreviewFn(): string { + const units: string = this.widgetConfig.config.units; + const decimals: number = this.widgetConfig.config.decimals; + return formatValue(22, decimals, units, true); + } + + private _datePreviewFn(): string { + const dateFormat: DateFormatSettings = this.valueCardWidgetSettingsForm.get('dateFormat').value; + const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat); + processor.update(Date.now()); + return processor.formatted; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html new file mode 100644 index 0000000000..ca5d4bc8b9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html @@ -0,0 +1,87 @@ + +
+
widgets.background.background-settings
+
+
+
widgets.background.background
+ + + {{ backgroundTypeTranslationsMap.get(type) | translate }} + + +
+ +
+
widgets.background.image-url
+ + + +
+
+
widgets.color.color
+ + +
+
+
+
widgets.background.overlay
+ + {{ 'widgets.background.enable-overlay' | translate }} + +
+
widgets.color.color
+ + +
+
+
widgets.background.blur
+ + +
px
+
+
+
+
+
+ widgets.background.preview +
+
+
+
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss new file mode 100644 index 0000000000..258117512a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss @@ -0,0 +1,73 @@ +/** + * 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. + */ + +@import '../../../../../../../../scss/constants'; + +.tb-background-settings-panel { + width: 620px; + display: flex; + flex-direction: column; + gap: 16px; + @media #{$mat-lt-md} { + width: 90vw; + } + .tb-background-settings-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-background-settings-preview { + flex: 1; + background: rgba(0, 0, 0, 0.04); + display: flex; + flex-direction: column; + padding: 12px 16px 24px 16px; + align-items: center; + gap: 12px; + } + .tb-background-settings-preview-title { + align-self: stretch; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 24px; + color: rgba(0, 0, 0, 0.38); + } + .tb-background-settings-preview-box { + position: relative; + width: 136px; + height: 118px; + border-radius: 2.666px; + } + .tb-background-settings-preview-overlay { + position: absolute; + border-radius: 2.666px; + top: 7.998px; + bottom: 7.998px; + left: 7.998px; + right: 7.998px; + } + .tb-background-settings-panel-buttons { + height: 40px; + display: flex; + flex-direction: row; + gap: 16px; + justify-content: flex-end; + align-items: flex-end; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts new file mode 100644 index 0000000000..51d2ddec1b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts @@ -0,0 +1,120 @@ +/// +/// 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. +/// + +import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { + backgroundStyle, + overlayStyle, + BackgroundSettings, + BackgroundType, + backgroundTypeTranslations, ComponentStyle +} from '@home/components/widget/config/widget-settings.models'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; + +@Component({ + selector: 'tb-background-settings-panel', + templateUrl: './background-settings-panel.component.html', + providers: [], + styleUrls: ['./background-settings-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class BackgroundSettingsPanelComponent extends PageComponent implements OnInit { + + @Input() + backgroundSettings: BackgroundSettings; + + @Input() + popover: TbPopoverComponent; + + @Output() + backgroundSettingsApplied = new EventEmitter(); + + backgroundType = BackgroundType; + + backgroundTypes = Object.keys(BackgroundType) as BackgroundType[]; + + backgroundTypeTranslationsMap = backgroundTypeTranslations; + + backgroundSettingsFormGroup: UntypedFormGroup; + + backgroundStyle: ComponentStyle = {}; + overlayStyle: ComponentStyle = {}; + + constructor(private fb: UntypedFormBuilder, + protected store: Store) { + super(store); + } + + ngOnInit(): void { + this.backgroundSettingsFormGroup = this.fb.group( + { + type: [this.backgroundSettings?.type, []], + imageBase64: [this.backgroundSettings?.imageBase64, []], + imageUrl: [this.backgroundSettings?.imageUrl, []], + color: [this.backgroundSettings?.color, []], + overlay: this.fb.group({ + enabled: [this.backgroundSettings?.overlay?.enabled, []], + color: [this.backgroundSettings?.overlay?.color, []], + blur: [this.backgroundSettings?.overlay?.blur, []] + }) + } + ); + this.backgroundSettingsFormGroup.get('type').valueChanges.subscribe(() => { + setTimeout(() => {this.popover?.updatePosition();}, 0); + }); + this.backgroundSettingsFormGroup.get('overlay').get('enabled').valueChanges.subscribe(() => { + this.updateValidators(); + }); + this.backgroundSettingsFormGroup.valueChanges.subscribe(() => { + this.updateBackgroundStyle(); + }); + this.updateValidators(); + this.updateBackgroundStyle(); + } + + cancel() { + this.popover?.hide(); + } + + applyColorSettings() { + const backgroundSettings = this.backgroundSettingsFormGroup.value; + this.backgroundSettingsApplied.emit(backgroundSettings); + } + + private updateValidators() { + const overlayEnabled: boolean = this.backgroundSettingsFormGroup.get('overlay').get('enabled').value; + if (overlayEnabled) { + this.backgroundSettingsFormGroup.get('overlay').get('color').enable(); + this.backgroundSettingsFormGroup.get('overlay').get('blur').enable(); + } else { + this.backgroundSettingsFormGroup.get('overlay').get('color').disable(); + this.backgroundSettingsFormGroup.get('overlay').get('blur').disable(); + } + this.backgroundSettingsFormGroup.get('overlay').get('color').updateValueAndValidity({emitEvent: false}); + this.backgroundSettingsFormGroup.get('overlay').get('blur').updateValueAndValidity({emitEvent: false}); + } + + private updateBackgroundStyle() { + const background: BackgroundSettings = this.backgroundSettingsFormGroup.value; + this.backgroundStyle = backgroundStyle(background); + this.overlayStyle = overlayStyle(background.overlay); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html new file mode 100644 index 0000000000..e9e1b99b0e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html @@ -0,0 +1,30 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.scss new file mode 100644 index 0000000000..6f73fbffa4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.scss @@ -0,0 +1,41 @@ +/** + * 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. + */ +button.mat-mdc-button-base.tb-box-button.tb-background-settings { + padding: 0; + .mat-mdc-button-persistent-ripple { + z-index: 2; + } + .tb-color-preview { + width: 38px; + min-width: 38px; + height: 38px; + &.box { + .tb-color-result { + &:after { + border: none; + } + } + .tb-color-overlay { + position: absolute; + border-radius: 3px; + top: 4px; + bottom: 4px; + left: 4px; + right: 4px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts new file mode 100644 index 0000000000..f8162575a3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts @@ -0,0 +1,120 @@ +/// +/// 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. +/// + +import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef, ViewEncapsulation } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { + BackgroundSettings, + backgroundStyle, + BackgroundType, + ComponentStyle, + overlayStyle +} from '@home/components/widget/config/widget-settings.models'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { + BackgroundSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/background-settings-panel.component'; + +@Component({ + selector: 'tb-background-settings', + templateUrl: './background-settings.component.html', + styleUrls: ['./background-settings.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => BackgroundSettingsComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class BackgroundSettingsComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled: boolean; + + backgroundType = BackgroundType; + + modelValue: BackgroundSettings; + + backgroundStyle: ComponentStyle = {}; + + overlayStyle: ComponentStyle = {}; + + private propagateChange = null; + + constructor(private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef) {} + + ngOnInit(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + this.updateBackgroundStyle(); + } + + writeValue(value: BackgroundSettings): void { + this.modelValue = value; + this.updateBackgroundStyle(); + } + + openBackgroundSettingsPopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + backgroundSettings: this.modelValue + }; + const backgroundSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, BackgroundSettingsPanelComponent, 'left', true, null, + ctx, + {}, + {}, {}, true); + backgroundSettingsPanelPopover.tbComponentRef.instance.popover = backgroundSettingsPanelPopover; + backgroundSettingsPanelPopover.tbComponentRef.instance.backgroundSettingsApplied.subscribe((backgroundSettings) => { + backgroundSettingsPanelPopover.hide(); + this.modelValue = backgroundSettings; + this.updateBackgroundStyle(); + this.propagateChange(this.modelValue); + }); + } + } + + private updateBackgroundStyle() { + if (!this.disabled) { + this.backgroundStyle = backgroundStyle(this.modelValue); + this.overlayStyle = overlayStyle(this.modelValue.overlay); + } else { + this.backgroundStyle = {}; + this.overlayStyle = {}; + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts index e538653703..d75c4a5fc9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts @@ -21,14 +21,14 @@ import { Directive, ElementRef, forwardRef, - Input, + Input, OnChanges, OnDestroy, OnInit, - QueryList, + QueryList, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms'; import { coerceBoolean } from '@shared/decorators/coercion'; -import { Observable, Subject } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; import { map, share, startWith, takeUntil } from 'rxjs/operators'; import { BreakpointObserver } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; @@ -73,7 +73,7 @@ export class ImageCardsSelectOptionDirective { ], encapsulation: ViewEncapsulation.None }) -export class ImageCardsSelectComponent implements ControlValueAccessor, OnInit, AfterContentInit, OnDestroy { +export class ImageCardsSelectComponent implements ControlValueAccessor, OnInit, OnChanges, AfterContentInit, OnDestroy { @ContentChildren(ImageCardsSelectOptionDirective) imageCardsSelectOptions: QueryList; @@ -107,20 +107,33 @@ export class ImageCardsSelectComponent implements ControlValueAccessor, OnInit, private _destroyed = new Subject(); + private _colsChanged = new BehaviorSubject(null); + constructor(private breakpointObserver: BreakpointObserver) { this.valueFormControl = new UntypedFormControl(''); } ngOnInit(): void { const gridColumns = this.breakpointObserver.isMatched(MediaBreakpoints['lt-md']) ? this.colsLtMd : this.cols; - this.cols$ = this.breakpointObserver - .observe(MediaBreakpoints['lt-md']).pipe( - map((state) => state.matches ? this.colsLtMd : this.cols), + this.cols$ = combineLatest({state: this.breakpointObserver + .observe(MediaBreakpoints['lt-md']), colsChanged: this._colsChanged.asObservable()}).pipe( + map((data) => data.state.matches ? this.colsLtMd : this.cols), startWith(gridColumns), share() ); } + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (['cols', 'colsLtMd'].includes(propName)) { + this._colsChanged.next(null); + } + } + } + } + ngAfterContentInit(): void { this.imageCardsSelectOptions.changes.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() => { this.syncImageCardsSelectOptions(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index 68b84578c1..748d90649d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -281,6 +281,13 @@ import { DateFormatSelectComponent } from '@home/components/widget/lib/settings/ import { DateFormatSettingsPanelComponent } from '@home/components/widget/lib/settings/common/date-format-settings-panel.component'; +import { BackgroundSettingsComponent } from '@home/components/widget/lib/settings/common/background-settings.component'; +import { + BackgroundSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/background-settings-panel.component'; +import { + ValueCardWidgetSettingsComponent +} from '@home/components/widget/lib/settings/cards/value-card-widget-settings.component'; @NgModule({ declarations: [ @@ -391,7 +398,10 @@ import { ColorSettingsPanelComponent, CssUnitSelectComponent, DateFormatSelectComponent, - DateFormatSettingsPanelComponent + DateFormatSettingsPanelComponent, + BackgroundSettingsComponent, + BackgroundSettingsPanelComponent, + ValueCardWidgetSettingsComponent ], imports: [ CommonModule, @@ -506,7 +516,10 @@ import { ColorSettingsPanelComponent, CssUnitSelectComponent, DateFormatSelectComponent, - DateFormatSettingsPanelComponent + DateFormatSettingsPanelComponent, + BackgroundSettingsComponent, + BackgroundSettingsPanelComponent, + ValueCardWidgetSettingsComponent ] }) export class WidgetSettingsModule { @@ -575,5 +588,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html index 6601aa96db..43b54884ec 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html @@ -19,7 +19,6 @@ [fullscreenBackgroundStyle]="dashboardStyle" [fullscreenBackgroundImage]="backgroundImage" (fullscreenChanged)="onFullscreenChanged($event)" - fxLayout="column" class="tb-widget" [ngClass]="{ 'tb-highlighted': isHighlighted(widget), @@ -32,8 +31,11 @@ (mousedown)="onMouseDown($event)" (click)="onClicked($event)" (contextmenu)="onContextMenu($event)"> -
-
+
+
-
- + diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss index a364189372..52caeb2a5c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss @@ -14,9 +14,14 @@ * limitations under the License. */ -tb-widget.tb-widget { - position: relative; - height: 100%; +.tb-widget-container { + position: absolute; + inset: 0; +} + +.tb-widget { + position: absolute; + inset: 0; margin: 0; overflow: hidden; outline: none; @@ -25,15 +30,27 @@ tb-widget.tb-widget { } div.tb-widget { - position: relative; - height: 100%; - margin: 0; - overflow: hidden; - outline: none; - - transition: all .2s ease-in-out; + display: flex; + flex-direction: column; + .tb-widget-header { + display: flex; + flex-direction: row; + place-content: flex-start space-between; + align-items: flex-start; + &-absolute { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 1; + } + } .tb-widget-title { + display: flex; + flex-direction: column; + place-content: flex-start center; + align-items: flex-start; max-height: 65px; padding-top: 5px; padding-left: 5px; @@ -63,6 +80,10 @@ div.tb-widget { } .tb-widget-actions { + display: flex; + flex-direction: row; + place-content: center flex-start; + align-items: center; z-index: 19; margin: 5px 0 0; @@ -104,13 +125,11 @@ div.tb-widget { } .tb-widget-content { + flex: 1; + position: relative; &.tb-no-interaction { pointer-events: none; } - tb-widget { - position: relative; - width: 100%; - } } &.tb-highlighted { diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index 7e9b9cb6e7..c276999d83 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -409,6 +409,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI elem.classList.add(this.widgetContext.widgetNamespace); this.widgetType = this.widgetInfo.widgetTypeFunction; this.typeParameters = this.widgetInfo.typeParameters; + this.widgetContext.absoluteHeader = this.typeParameters.absoluteHeader; if (!this.widgetType) { this.widgetTypeInstance = {}; diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index b16e880e02..18c8ccd19e 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -265,6 +265,8 @@ export class WidgetContext { hiddenData?: Array<{data: DataSet}>; timeWindow?: WidgetTimewindow; + absoluteHeader?: boolean; + hideTitlePanel = false; widgetTitle?: string; diff --git a/ui-ngx/src/app/shared/components/unit-input.component.html b/ui-ngx/src/app/shared/components/unit-input.component.html index d001a43ef2..0ae14b8ba9 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.html +++ b/ui-ngx/src/app/shared/components/unit-input.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + > { if (this.fetchUnits$ === null) { - this.fetchUnits$ = this.resourcesService.loadJsonResource>(unitsModels).pipe( + this.fetchUnits$ = getUnits(this.resourcesService).pipe( map(units => units.map(u => ({ symbol: u.symbol, name: this.translate.instant(u.name), diff --git a/ui-ngx/src/app/shared/models/unit.models.ts b/ui-ngx/src/app/shared/models/unit.models.ts index 797e8a0c4a..7d9f88a068 100644 --- a/ui-ngx/src/app/shared/models/unit.models.ts +++ b/ui-ngx/src/app/shared/models/unit.models.ts @@ -14,6 +14,9 @@ /// limitations under the License. /// +import { ResourcesService } from '@core/services/resources.service'; +import { Observable } from 'rxjs'; + export interface Unit { name: string; symbol: string; @@ -30,3 +33,6 @@ export const searchUnits = (_units: Array, searchText: string): Array> => + resourcesService.loadJsonResource('/assets/metadata/units.json'); diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 716a4cf8b4..e0d9918540 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -19,7 +19,6 @@ import { TenantId } from '@shared/models/id/tenant-id'; import { WidgetTypeId } from '@shared/models/id/widget-type-id'; import { AggregationType, ComparisonDuration, Timewindow } from '@shared/models/time/time.models'; import { EntityType } from '@shared/models/entity-type.models'; -import { AlarmSearchStatus, AlarmSeverity } from '@shared/models/alarm.models'; import { DataKeyType } from './telemetry/telemetry.models'; import { EntityId } from '@shared/models/id/entity-id'; import * as moment_ from 'moment'; @@ -40,6 +39,7 @@ import { Observable } from 'rxjs'; import { Dashboard } from '@shared/models/dashboard.models'; import { IAliasController } from '@core/api/widget-api.models'; import { isEmptyStr } from '@core/utils'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; export enum widgetType { timeseries = 'timeseries', @@ -182,6 +182,7 @@ export interface WidgetTypeParameters { processNoDataByWidget?: boolean; previewWidth?: string; previewHeight?: string; + absoluteHeader?: boolean; } export interface WidgetControllerDescriptor { @@ -706,6 +707,7 @@ export interface IWidgetSettingsComponent { aliasController: IAliasController; dashboard: Dashboard; widget: Widget; + widgetConfig: WidgetConfigComponentData; functionScopeVariables: string[]; settings: WidgetSettings; settingsChanged: Observable; @@ -737,6 +739,17 @@ export abstract class WidgetSettingsComponent extends PageComponent implements widget: Widget; + widgetConfigValue: WidgetConfigComponentData; + + set widgetConfig(value: WidgetConfigComponentData) { + this.widgetConfigValue = value; + this.onWidgetConfigSet(value); + } + + get widgetConfig(): WidgetConfigComponentData { + return this.widgetConfigValue; + } + functionScopeVariables: string[]; settingsValue: WidgetSettings; @@ -848,4 +861,7 @@ export abstract class WidgetSettingsComponent extends PageComponent implements return {}; } + protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) { + } + } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 0f96a5f1dc..536c2dcc4d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4690,6 +4690,7 @@ "advanced-widget-style": "Advanced widget style", "card-buttons": "Card buttons", "show-card-buttons": "Show card buttons", + "card-border-radius": "Card border radius", "card-appearance": "Card appearance", "color": "Color" }, @@ -4702,6 +4703,18 @@ "invalid-widget-type-file-error": "Unable to import widget type: Invalid widget type data structure." }, "widgets": { + "background": { + "background": "Background", + "background-settings": "Background settings", + "background-type-image": "Upload image", + "background-type-image-url": "Image URL", + "background-type-color": "Solid color", + "image-url": "Image URL", + "overlay": "Overlay", + "enable-overlay": "Enable overlay", + "blur": "Blur", + "preview": "Preview" + }, "chart": { "common-settings": "Common settings", "enable-stacking-mode": "Enable stacking mode", @@ -5665,7 +5678,8 @@ "label": "Label", "icon": "Icon", "value": "Value", - "date": "Date" + "date": "Date", + "value-card-style": "Value card style" }, "table": { "common-table-settings": "Common Table Settings", diff --git a/ui-ngx/src/assets/model/units.json b/ui-ngx/src/assets/metadata/units.json similarity index 100% rename from ui-ngx/src/assets/model/units.json rename to ui-ngx/src/assets/metadata/units.json diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index d8bbdf743d..75fc0845cf 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -306,10 +306,7 @@ pre.tb-highlight { .tb-fullscreen { position: fixed !important; - top: 0; - left: 0; - width: 100% !important; - height: 100% !important; + inset: 0 !important; } .tb-fullscreen-parent { @@ -983,10 +980,7 @@ mat-label { min-width: 100%; max-width: none !important; position: absolute !important; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; .mat-mdc-dialog-container { > *:first-child, form { min-width: 100% !important; @@ -1004,10 +998,7 @@ mat-label { min-width: 100%; max-width: none !important; position: absolute !important; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; .mat-mdc-dialog-container { > *:first-child, form { min-width: 100% !important; @@ -1022,10 +1013,7 @@ mat-label { .tb-absolute-fill { position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; } .tb-layout-fill { @@ -1037,10 +1025,7 @@ mat-label { .tb-progress-cover { position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; + inset: 0; z-index: 6; background-color: #eee; opacity: 1; From dc3f3ceafbfbf9cc06d402c1a8e0bc5c16b77094 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 27 Jul 2023 17:23:53 +0300 Subject: [PATCH 088/166] UI: Add color picker input for multiple input widget --- .../widget/lib/multiple-input-widget.component.html | 11 +++++++++++ .../widget/lib/multiple-input-widget.component.ts | 2 +- ...te-multiple-attributes-key-settings.component.html | 3 +++ ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 0a757fd34f..c0886046c0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -172,6 +172,17 @@
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index 534be7c676..05a972e296 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -54,7 +54,7 @@ type FieldAlignment = 'row' | 'column'; type MultipleInputWidgetDataKeyType = 'server' | 'shared' | 'timeseries'; export type MultipleInputWidgetDataKeyValueType = 'string' | 'double' | 'integer' | 'JSON' | 'booleanCheckbox' | 'booleanSwitch' | - 'dateTime' | 'date' | 'time' | 'select'; + 'dateTime' | 'date' | 'time' | 'select' | 'colorPicker'; type MultipleInputWidgetDataKeyEditableType = 'editable' | 'disabled' | 'readonly'; type ConvertGetValueFunction = (value: any, ctx: WidgetContext) => any; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html index 3c62810000..22eb191ec3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html @@ -69,6 +69,9 @@ {{ 'widgets.input-widgets.datakey-value-type-json' | translate }} + + {{ 'widgets.input-widgets.datakey-value-type-color-picker' | translate }} + diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index c5ec1fca40..fbfb96cac0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4792,6 +4792,7 @@ "datakey-value-type-date": "Date", "datakey-value-type-time": "Time", "datakey-value-type-select": "Select", + "datakey-value-type-color-picker": "Color Picker", "value-is-required": "Value is required", "ability-to-edit-attribute": "Ability to edit attribute", "ability-to-edit-attribute-editable": "Editable (default)", From 80fbc89e20b8a78b79cc150d9df436c89855423e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 27 Jul 2023 17:39:04 +0300 Subject: [PATCH 089/166] UI: use .mat-icon class selector instead of mat-icon tag for tb-icon component compatibility. --- .../components/attribute/attribute-table.component.scss | 2 +- .../home/components/widget/config/data-keys.component.scss | 2 +- .../widget/lib/edges-overview-widget.component.scss | 4 ++-- .../widget/lib/entities-hierarchy-widget.component.scss | 4 ++-- .../widget/lib/navigation-card-widget.component.scss | 2 +- .../widget/lib/trip-animation/trip-animation.component.scss | 2 +- ui-ngx/src/app/modules/home/menu/side-menu.component.scss | 2 +- .../home/pages/rulechain/rulechain-page.component.scss | 4 ++-- .../modules/home/pages/rulechain/rulenode.component.scss | 2 +- .../modules/home/pages/widget/widget-editor.component.scss | 2 +- ui-ngx/src/app/shared/components/fab-toolbar.component.scss | 6 +++--- .../time/history-selector/history-selector.component.scss | 4 ++-- ui-ngx/src/app/shared/components/user-menu.component.scss | 2 +- ui-ngx/src/theme.scss | 2 +- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss index 831762d1e4..b33dfdbb20 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss @@ -104,7 +104,7 @@ } mat-cell.tb-value-cell { cursor: pointer; - mat-icon { + .mat-icon { height: 24px; width: 24px; font-size: 24px; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss index 415c69ec53..1664dafb7f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss @@ -49,7 +49,7 @@ padding: 3px; height: 24px; cursor: move; - mat-icon { + .mat-icon { pointer-events: none; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.scss index f5844d8aac..9b2d35e030 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.scss @@ -71,7 +71,7 @@ background-size: 18px 18px; } - mat-icon.node-icon { + .mat-icon.node-icon { width: 22px; min-width: 22px; height: 22px; @@ -109,7 +109,7 @@ background-size: 24px 24px; } - mat-icon.node-icon { + .mat-icon.node-icon { width: 40px; min-width: 40px; height: 40px; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss index 6731690b0b..426d81b723 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss @@ -64,7 +64,7 @@ background-size: 18px 18px; } - mat-icon.node-icon { + .mat-icon.node-icon { width: 22px; min-width: 22px; height: 22px; @@ -102,7 +102,7 @@ background-size: 24px 24px; } - mat-icon.node-icon { + .mat-icon.node-icon { width: 40px; min-width: 40px; height: 40px; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss index a04f82dce7..b9c3e034a7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss @@ -31,7 +31,7 @@ display: flex; flex-direction: column; align-items: center; - mat-icon { + .mat-icon { margin: auto !important; } span.mdc-button__label { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/trip-animation/trip-animation.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/trip-animation/trip-animation.component.scss index d379c9ff8a..4118a26800 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/trip-animation/trip-animation.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/trip-animation/trip-animation.component.scss @@ -54,7 +54,7 @@ line-height: 24px; z-index: 999; - mat-icon { + .mat-icon { width: 24px; height: 24px; diff --git a/ui-ngx/src/app/modules/home/menu/side-menu.component.scss b/ui-ngx/src/app/modules/home/menu/side-menu.component.scss index fc9df865a1..dbba5e78a9 100644 --- a/ui-ngx/src/app/modules/home/menu/side-menu.component.scss +++ b/ui-ngx/src/app/modules/home/menu/side-menu.component.scss @@ -49,7 +49,7 @@ &.tb-active { background-color: rgba(255, 255, 255, .15); } - mat-icon { + .mat-icon { margin-right: 8px; margin-left: 0; min-width: 1.125rem; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss index b109b4753d..db8d6322f3 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss @@ -117,7 +117,7 @@ min-height: 32px; padding: 6px; line-height: 20px; - mat-icon { + .mat-icon { width: 20px; min-width: 20px; height: 20px; @@ -216,7 +216,7 @@ cursor: pointer; box-sizing: border-box; - mat-icon{ + .mat-icon{ width: 16px; min-width: 16px; height: 16px; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.scss index 38ef76feaa..0811288423 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.scss +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.scss @@ -86,7 +86,7 @@ background-color: #a3eaa9; } - mat-icon, img { + .mat-icon, img { margin: auto; width: 20px; min-width: 20px; diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss index b8359b38db..f928dde955 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss @@ -185,7 +185,7 @@ mat-toolbar.tb-edit-toolbar { white-space: nowrap; height: 28px; - mat-icon { + .mat-icon { height: 20px; width: 20px; font-size: 20px; diff --git a/ui-ngx/src/app/shared/components/fab-toolbar.component.scss b/ui-ngx/src/app/shared/components/fab-toolbar.component.scss index e8e0c0b9f2..42f2e0c9eb 100644 --- a/ui-ngx/src/app/shared/components/fab-toolbar.component.scss +++ b/ui-ngx/src/app/shared/components/fab-toolbar.component.scss @@ -74,7 +74,7 @@ mat-fab-toolbar { button.mat-mdc-fab { overflow: visible !important; opacity: .5; - mat-icon { + .mat-icon { position: relative; z-index: $z-index-fab + 2; opacity: 1; @@ -146,7 +146,7 @@ mat-fab-toolbar { box-shadow: none; opacity: 1; - mat-icon { + .mat-icon { opacity: 0; } } @@ -163,7 +163,7 @@ mat-fab-toolbar { mat-fab-trigger { button.mat-mdc-fab { transition: opacity .3s cubic-bezier(.55, 0, .55, .2) .2s; - mat-icon { + .mat-icon { transition: all $icon-delay ease-in; } } diff --git a/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.scss b/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.scss index 6f38e6a6a5..f6f24e2608 100644 --- a/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.scss +++ b/ui-ngx/src/app/shared/components/time/history-selector/history-selector.component.scss @@ -51,7 +51,7 @@ margin: 2px; line-height: 24px; - mat-icon { + .mat-icon { width: 24px; height: 24px; @@ -93,7 +93,7 @@ margin: 0; line-height: 28px; - mat-icon { + .mat-icon { width: 24px; height: 24px; font-size: 24px; diff --git a/ui-ngx/src/app/shared/components/user-menu.component.scss b/ui-ngx/src/app/shared/components/user-menu.component.scss index b0d3acbf51..c435fe2867 100644 --- a/ui-ngx/src/app/shared/components/user-menu.component.scss +++ b/ui-ngx/src/app/shared/components/user-menu.component.scss @@ -36,7 +36,7 @@ } - mat-icon.tb-mini-avatar { + .mat-icon.tb-mini-avatar { width: 36px; height: 36px; margin: auto 8px; diff --git a/ui-ngx/src/theme.scss b/ui-ngx/src/theme.scss index 9aa3e61d39..df1b2bca3e 100644 --- a/ui-ngx/src/theme.scss +++ b/ui-ngx/src/theme.scss @@ -212,7 +212,7 @@ $tb-dark-theme: map_merge($tb-dark-theme, $color); &.mat-primary { @include _mat-toolbar-inverse-color($primary); button.mat-mdc-icon-button { - mat-icon { + .mat-icon { color: mat.get-color-from-palette($primary); } } From 0f5841e9cb3cbf70c8946f17b5abd87b87144edd Mon Sep 17 00:00:00 2001 From: rusikv Date: Thu, 27 Jul 2023 18:07:50 +0300 Subject: [PATCH 090/166] Added dialog for creation latest telemetry key value --- .../add-attribute-dialog.component.html | 2 +- .../add-attribute-dialog.component.ts | 27 ++++++++++++------- .../attribute/attribute-table.component.html | 2 +- .../attribute/attribute-table.component.ts | 2 +- .../assets/locale/locale.constant-en_US.json | 3 ++- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html index c48299986e..17e31854cc 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html @@ -17,7 +17,7 @@ -->
-

{{ 'attribute.add' | translate }}

+

{{ title | translate }}

-
- - -
+ +
From a659d1b7e6c8614923e4d9b1e42df524165dabab Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 28 Jul 2023 11:02:27 +0300 Subject: [PATCH 101/166] UI: Change value type for color --- .../widget/lib/multiple-input-widget.component.html | 2 +- .../components/widget/lib/multiple-input-widget.component.ts | 2 +- .../update-multiple-attributes-key-settings.component.html | 4 ++-- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 9d5bf1420c..fa51899c4a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -173,7 +173,7 @@
any; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html index 22eb191ec3..d69a4b0713 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html @@ -69,8 +69,8 @@ {{ 'widgets.input-widgets.datakey-value-type-json' | translate }} - - {{ 'widgets.input-widgets.datakey-value-type-color-picker' | translate }} + + {{ 'widgets.input-widgets.datakey-value-type-color' | translate }} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index fbfb96cac0..af441665bd 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4792,7 +4792,7 @@ "datakey-value-type-date": "Date", "datakey-value-type-time": "Time", "datakey-value-type-select": "Select", - "datakey-value-type-color-picker": "Color Picker", + "datakey-value-type-color": "Color", "value-is-required": "Value is required", "ability-to-edit-attribute": "Ability to edit attribute", "ability-to-edit-attribute-editable": "Editable (default)", From 3f18c2e43636633766bdc0fdb4dce787a06e66bd Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 28 Jul 2023 11:57:32 +0300 Subject: [PATCH 102/166] UI: update label --- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index e0c07480cf..9ce52d14ab 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -938,7 +938,7 @@ "selected-customers": "{ count, plural, =1 {1 customer} other {# customers} } selected", "edges": "Customer edge instances", "manage-edges": "Manage edges", - "assign-customer": "Assign customer" + "assign-customer": "Assign to customer" }, "datetime": { "date-from": "Date from", From aec44cf72c335cff6eb2adbacca93b38915174a0 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 28 Jul 2023 12:06:30 +0300 Subject: [PATCH 103/166] UI: Refactoring --- .../home/components/wizard/device-wizard-dialog.component.html | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 08d71e1229..fd427923f7 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -76,7 +76,7 @@
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 9ce52d14ab..42fb38ed54 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -937,8 +937,7 @@ "search": "Search customers", "selected-customers": "{ count, plural, =1 {1 customer} other {# customers} } selected", "edges": "Customer edge instances", - "manage-edges": "Manage edges", - "assign-customer": "Assign to customer" + "manage-edges": "Manage edges" }, "datetime": { "date-from": "Date from", From d9c39c362eba7c579061b1a7a75248d2effaf3e4 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 28 Jul 2023 14:20:33 +0200 Subject: [PATCH 104/166] refactored due to comments --- .../src/main/resources/thingsboard.yml | 2 +- .../queue/discovery/ZkDiscoveryService.java | 26 +++++--- .../discovery/ZkDiscoveryServiceTest.java | 62 ++++++++++++------- .../src/main/resources/tb-vc-executor.yml | 2 +- .../src/main/resources/tb-coap-transport.yml | 2 +- .../src/main/resources/tb-http-transport.yml | 2 +- .../src/main/resources/tb-lwm2m-transport.yml | 2 +- .../src/main/resources/tb-mqtt-transport.yml | 2 +- .../src/main/resources/tb-snmp-transport.yml | 2 +- 9 files changed, 62 insertions(+), 40 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 9cec475335..b6fd99dcec 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -96,7 +96,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cluster: stats: diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 50378d3387..44999d016a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.queue.discovery; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.ProtocolStringList; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.curator.framework.CuratorFramework; @@ -68,7 +69,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi private Integer zkSessionTimeout; @Value("${zk.zk_dir}") private String zkDir; - @Value("${zk.recalculate_delay:120000}") + @Value("${zk.recalculate_delay:60000}") private Long recalculateDelay; protected final ConcurrentHashMap> delayedTasks; @@ -294,35 +295,39 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi log.error("Failed to decode server instance for node {}", data.getPath(), e); throw e; } - log.debug("Processing [{}] event for [{}]", pathChildrenCacheEvent.getType(), instance.getServiceId()); + + String serviceId = instance.getServiceId(); + ProtocolStringList serviceTypesList = instance.getServiceTypesList(); + + log.trace("Processing [{}] event for [{}]", pathChildrenCacheEvent.getType(), serviceId); switch (pathChildrenCacheEvent.getType()) { case CHILD_ADDED: - ScheduledFuture task = delayedTasks.remove(instance.getServiceId()); + ScheduledFuture task = delayedTasks.remove(serviceId); if (task != null) { if (task.cancel(false)) { log.debug("[{}] Recalculate partitions ignored. Service was restarted in time [{}].", - instance.getServiceId(), instance.getServiceTypesList()); + serviceId, serviceTypesList); } else { log.debug("[{}] Going to recalculate partitions. Service was not restarted in time [{}]!", - instance.getServiceId(), instance.getServiceTypesList()); + serviceId, serviceTypesList); recalculatePartitions(); } } else { - log.debug("[{}] Going to recalculate partitions due to adding new node [{}].", - instance.getServiceId(), instance.getServiceTypesList()); + log.trace("[{}] Going to recalculate partitions due to adding new node [{}].", + serviceId, serviceTypesList); recalculatePartitions(); } break; case CHILD_REMOVED: ScheduledFuture future = zkExecutorService.schedule(() -> { log.debug("[{}] Going to recalculate partitions due to removed node [{}]", - instance.getServiceId(), instance.getServiceTypesList()); - ScheduledFuture removedTask = delayedTasks.remove(instance.getServiceId()); + serviceId, serviceTypesList); + ScheduledFuture removedTask = delayedTasks.remove(serviceId); if (removedTask != null) { recalculatePartitions(); } }, recalculateDelay, TimeUnit.MILLISECONDS); - delayedTasks.put(instance.getServiceId(), future); + delayedTasks.put(serviceId, future); break; default: break; @@ -334,6 +339,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi * Synchronized to ensure that other servers info is up to date * */ synchronized void recalculatePartitions() { + delayedTasks.values().forEach(future -> future.cancel(false)); delayedTasks.clear(); partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers()); } diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java index 38cad217aa..a8810efd0e 100644 --- a/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java +++ b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java @@ -63,68 +63,76 @@ public class ZkDiscoveryServiceTest { @Mock private PathChildrenCache cache; - private ScheduledExecutorService zkExecutorService; - @Mock private CuratorFramework curatorFramework; private ZkDiscoveryService zkDiscoveryService; + private static final long RECALCULATE_DELAY = 100L; + + final TransportProtos.ServiceInfo currentInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("tb-rule-engine-0").build(); + final ChildData currentData = new ChildData("/thingsboard/nodes/0000000010", null, currentInfo.toByteArray()); + final TransportProtos.ServiceInfo childInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("tb-rule-engine-1").build(); + final ChildData childData = new ChildData("/thingsboard/nodes/0000000020", null, childInfo.toByteArray()); + @Before public void setup() { zkDiscoveryService = Mockito.spy(new ZkDiscoveryService(serviceInfoProvider, partitionService)); - zkExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("zk-discovery")); + ScheduledExecutorService zkExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("zk-discovery")); when(client.getState()).thenReturn(CuratorFrameworkState.STARTED); ReflectionTestUtils.setField(zkDiscoveryService, "stopped", false); ReflectionTestUtils.setField(zkDiscoveryService, "client", client); ReflectionTestUtils.setField(zkDiscoveryService, "cache", cache); ReflectionTestUtils.setField(zkDiscoveryService, "nodePath", "/thingsboard/nodes/0000000010"); ReflectionTestUtils.setField(zkDiscoveryService, "zkExecutorService", zkExecutorService); - ReflectionTestUtils.setField(zkDiscoveryService, "recalculateDelay", 1000L); + ReflectionTestUtils.setField(zkDiscoveryService, "recalculateDelay", RECALCULATE_DELAY); ReflectionTestUtils.setField(zkDiscoveryService, "zkDir", "/thingsboard"); - } - - @Test - public void restartNodeTest() throws Exception { - var currentInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("currentId").build(); - var currentData = new ChildData("/thingsboard/nodes/0000000010", null, currentInfo.toByteArray()); - var childInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("childId").build(); - var childData = new ChildData("/thingsboard/nodes/0000000020", null, childInfo.toByteArray()); when(serviceInfoProvider.getServiceInfo()).thenReturn(currentInfo); + List dataList = new ArrayList<>(); dataList.add(currentData); when(cache.getCurrentData()).thenReturn(dataList); + } + @Test + public void restartNodeInTimeTest() throws Exception { startNode(childData); verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); reset(partitionService); - //Restart in timeAssert.assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); stopNode(childData); assertEquals(1, zkDiscoveryService.delayedTasks.size()); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); startNode(childData); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); - Thread.sleep(2000); + Thread.sleep(RECALCULATE_DELAY * 2); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + } + + @Test + public void restartNodeNotInTimeTest() throws Exception { + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); - //Restart not in time stopNode(childData); assertEquals(1, zkDiscoveryService.delayedTasks.size()); - Thread.sleep(2000); + Thread.sleep(RECALCULATE_DELAY * 2); assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); @@ -135,11 +143,19 @@ public class ZkDiscoveryServiceTest { verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); reset(partitionService); + } - //Start another node during restart - var anotherInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("anotherId").build(); + @Test + public void startAnotherNodeDuringRestartTest() throws Exception { + var anotherInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("tb-transport").build(); var anotherData = new ChildData("/thingsboard/nodes/0000000030", null, anotherInfo.toByteArray()); + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); + stopNode(childData); assertEquals(1, zkDiscoveryService.delayedTasks.size()); @@ -151,9 +167,9 @@ public class ZkDiscoveryServiceTest { verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(anotherInfo))); reset(partitionService); - Thread.sleep(2000); + Thread.sleep(RECALCULATE_DELAY * 2); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); startNode(childData); diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml index 2c90082eb5..1c567588df 100644 --- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml +++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" queue: type: "${TB_QUEUE_TYPE:kafka}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index aef46a1234..1f8861ced9 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 4bce6e28d7..7c5103cfac 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -68,7 +68,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index eab5b107c8..4ab59aec01 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index f0968aa6b9..a103edf1f4 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index c7dcd70574..44a86dc6dd 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" From b71ae531bb79db83231d051bab8e32e8a53cdea9 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 28 Jul 2023 15:51:21 +0300 Subject: [PATCH 105/166] UI: Clear code and rename state action --- ui-ngx/src/app/core/auth/auth.actions.ts | 8 ++++---- ui-ngx/src/app/core/auth/auth.effects.ts | 4 ++-- ui-ngx/src/app/core/auth/auth.reducer.ts | 2 +- ui-ngx/src/app/core/utils.ts | 4 +++- .../device/device-check-connectivity-dialog.component.ts | 4 ++-- ui-ngx/src/app/shared/components/markdown.component.scss | 2 +- ui-ngx/src/form.scss | 7 ------- 7 files changed, 13 insertions(+), 18 deletions(-) diff --git a/ui-ngx/src/app/core/auth/auth.actions.ts b/ui-ngx/src/app/core/auth/auth.actions.ts index 2e8c82ae2d..9e5640a97d 100644 --- a/ui-ngx/src/app/core/auth/auth.actions.ts +++ b/ui-ngx/src/app/core/auth/auth.actions.ts @@ -27,7 +27,7 @@ export enum AuthActionTypes { UPDATE_LAST_PUBLIC_DASHBOARD_ID = '[Auth] Update Last Public Dashboard Id', UPDATE_HAS_REPOSITORY = '[Auth] Change Has Repository', UPDATE_OPENED_MENU_SECTION = '[Preferences] Update Opened Menu Section', - UPDATE_USER_SETTINGS = '[Preferences] Update user settings', + PUT_USER_SETTINGS = '[Preferences] Put user settings', DELETE_USER_SETTINGS = '[Preferences] Delete user settings', } @@ -71,8 +71,8 @@ export class ActionPreferencesUpdateOpenedMenuSection implements Action { constructor(readonly payload: { path: string; opened: boolean }) {} } -export class ActionPreferencesUpdateUserSettings implements Action { - readonly type = AuthActionTypes.UPDATE_USER_SETTINGS; +export class ActionPreferencesPutUserSettings implements Action { + readonly type = AuthActionTypes.PUT_USER_SETTINGS; constructor(readonly payload: Partial) {} } @@ -85,4 +85,4 @@ export class ActionPreferencesDeleteUserSettings implements Action { export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated | ActionAuthLoadUser | ActionAuthUpdateUserDetails | ActionAuthUpdateLastPublicDashboardId | ActionAuthUpdateHasRepository | - ActionPreferencesUpdateOpenedMenuSection | ActionPreferencesUpdateUserSettings | ActionPreferencesDeleteUserSettings; + ActionPreferencesUpdateOpenedMenuSection | ActionPreferencesPutUserSettings | ActionPreferencesDeleteUserSettings; diff --git a/ui-ngx/src/app/core/auth/auth.effects.ts b/ui-ngx/src/app/core/auth/auth.effects.ts index 76b9dce9fa..3e5eb28d72 100644 --- a/ui-ngx/src/app/core/auth/auth.effects.ts +++ b/ui-ngx/src/app/core/auth/auth.effects.ts @@ -40,9 +40,9 @@ export class AuthEffects { mergeMap(([action, state]) => this.userSettingsService.putUserSettings({ openedMenuSections: state.userSettings.openedMenuSections })) ), {dispatch: false}); - updatedUserSettings = createEffect(() => this.actions$.pipe( + putUserSettings = createEffect(() => this.actions$.pipe( ofType( - AuthActionTypes.UPDATE_USER_SETTINGS, + AuthActionTypes.PUT_USER_SETTINGS, ), mergeMap((state) => this.userSettingsService.putUserSettings(state.payload)) ), {dispatch: false}); diff --git a/ui-ngx/src/app/core/auth/auth.reducer.ts b/ui-ngx/src/app/core/auth/auth.reducer.ts index 6fd80d7052..4bcf71104b 100644 --- a/ui-ngx/src/app/core/auth/auth.reducer.ts +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -76,7 +76,7 @@ export const authReducer = ( userSettings = {...state.userSettings, ...{ openedMenuSections: Array.from(openedMenuSections)}}; return { ...state, ...{ userSettings }}; - case AuthActionTypes.UPDATE_USER_SETTINGS: + case AuthActionTypes.PUT_USER_SETTINGS: userSettings = {...state.userSettings, ...action.payload}; return { ...state, ...{ userSettings }}; diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index c823c2bfea..9a369cb5aa 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -355,7 +355,9 @@ const SNAKE_CASE_REGEXP = /[A-Z]/g; export function snakeCase(name: string, separator: string): string { separator = separator || '_'; - return name.replace(SNAKE_CASE_REGEXP, (letter, pos) => (pos ? separator : '') + letter.toLowerCase()); + return name.replace(SNAKE_CASE_REGEXP, (letter, pos) => { + return (pos ? separator : '') + letter.toLowerCase(); + }); } export function getDescendantProp(obj: any, path: string): any { diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts index 2135a3aeea..7516e0f3e1 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts @@ -40,7 +40,7 @@ import { NetworkTransportType, PublishTelemetryCommand } from '@shared/models/device.models'; -import { ActionPreferencesUpdateUserSettings } from '@core/auth/auth.actions'; +import { ActionPreferencesPutUserSettings } from '@core/auth/auth.actions'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { getOS } from '@core/utils'; @@ -121,7 +121,7 @@ export class DeviceCheckConnectivityDialogComponent extends close(): void { if (this.notShowAgain && this.showDontShowAgain) { - this.store.dispatch(new ActionPreferencesUpdateUserSettings({ notDisplayConnectivityAfterAddDevice: true })); + this.store.dispatch(new ActionPreferencesPutUserSettings({ notDisplayConnectivityAfterAddDevice: true })); this.dialogRef.close(null); } else { this.dialogRef.close(null); diff --git a/ui-ngx/src/app/shared/components/markdown.component.scss b/ui-ngx/src/app/shared/components/markdown.component.scss index e23111fc6b..757a26c587 100644 --- a/ui-ngx/src/app/shared/components/markdown.component.scss +++ b/ui-ngx/src/app/shared/components/markdown.component.scss @@ -88,7 +88,7 @@ } } - a:not(.ignore-style-a) { + a { font-weight: 500; color: #2a7dec; text-decoration: none; diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index bb82e937bf..00e9492af0 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -152,13 +152,6 @@ &.space-between { justify-content: space-between; } - &.no-border { - border: none; - border-radius: 0; - } - &.no-padding { - padding: 0; - } .mat-divider-vertical { height: 56px; margin-top: -7px; From 907c8f3e1c644c8a359e9ec704ce9b8fafc3597d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 28 Jul 2023 16:58:12 +0300 Subject: [PATCH 106/166] UI: Optimize gets tabs in routerTabs components --- .../home/components/router-tabs.component.ts | 49 +++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/router-tabs.component.ts b/ui-ngx/src/app/modules/home/components/router-tabs.component.ts index c5ffb11908..5735499262 100644 --- a/ui-ngx/src/app/modules/home/components/router-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/components/router-tabs.component.ts @@ -20,8 +20,8 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { MenuService } from '@core/services/menu.service'; -import { distinctUntilChanged, filter, map, mergeMap, take } from 'rxjs/operators'; -import { merge } from 'rxjs'; +import { distinctUntilChanged, filter, map, mergeMap, startWith, take } from 'rxjs/operators'; +import { merge, Observable } from 'rxjs'; import { MenuSection } from '@core/services/menu.models'; import { ActiveComponentService } from '@core/services/active-component.service'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; @@ -39,14 +39,7 @@ export class RouterTabsComponent extends PageComponent implements OnInit { hideCurrentTabs = false; - tabs$ = merge(this.menuService.menuSections(), - this.router.events.pipe( - filter((event) => event instanceof NavigationEnd ), - distinctUntilChanged()) - ).pipe( - mergeMap(() => this.menuService.menuSections().pipe(take(1))), - map((sections) => this.buildTabs(this.activatedRoute, sections)) - ); + tabs$: Observable>; constructor(protected store: Store, private activatedRoute: ActivatedRoute, @@ -57,6 +50,23 @@ export class RouterTabsComponent extends PageComponent implements OnInit { } ngOnInit() { + if (this.activatedRoute.snapshot.data.useChildrenRoutesForTabs) { + this.tabs$ = this.router.events.pipe( + filter((event) => event instanceof NavigationEnd), + startWith(''), + map(() => this.buildTabsForRoutes(this.activatedRoute)) + ); + } else { + this.tabs$ = merge(this.menuService.menuSections(), + this.router.events.pipe( + filter((event) => event instanceof NavigationEnd ), + distinctUntilChanged()) + ).pipe( + mergeMap(() => this.menuService.menuSections().pipe(take(1))), + map((sections) => this.buildTabs(this.activatedRoute, sections)) + ); + } + this.activatedRoute.data.subscribe( (data) => this.buildTabsHeaderComponent(data) ); @@ -80,16 +90,26 @@ export class RouterTabsComponent extends PageComponent implements OnInit { } } - private buildTabs(activatedRoute: ActivatedRoute, sections: MenuSection[]): Array { - const sectionPath = '/' + activatedRoute.pathFromRoot.map(r => r.snapshot.url) + private getSectionPath(activatedRoute: ActivatedRoute): string { + return '/' + activatedRoute.pathFromRoot.map(r => r.snapshot.url) .filter(f => !!f[0]).map(f => f.map(f1 => f1.path).join('/')).join('/'); + } + + private buildTabs(activatedRoute: ActivatedRoute, sections: MenuSection[]): Array { + const sectionPath = this.getSectionPath(activatedRoute); const found = this.findRootSection(sections, sectionPath); if (found) { const rootPath = sectionPath.substring(0, sectionPath.length - found.path.length); const isRoot = rootPath === ''; const tabs: Array = found ? found.pages.filter(page => !page.disabled && (!page.rootOnly || isRoot)) : []; return tabs.map((tab) => ({...tab, path: rootPath + tab.path})); - } else if (activatedRoute.snapshot.data.useChildrenRoutesForTabs && sectionPath.endsWith(activatedRoute.routeConfig.path)) { + } + return []; + } + + private buildTabsForRoutes(activatedRoute: ActivatedRoute): Array { + const sectionPath = this.getSectionPath(activatedRoute); + if (activatedRoute.routeConfig.children.length) { const activeRouterChildren = activatedRoute.routeConfig.children.filter(page => page.path !== ''); return activeRouterChildren.map(tab => ({ id: tab.component.name, @@ -98,9 +118,8 @@ export class RouterTabsComponent extends PageComponent implements OnInit { icon: tab.data?.breadcrumb?.icon ?? '', path: `${sectionPath}/${tab.path}` })); - } else { - return []; } + return []; } private findRootSection(sections: MenuSection[], sectionPath: string): MenuSection { From 5b2918de9589bbdd763dbfe1317a5b3c11d869a4 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 28 Jul 2023 17:27:38 +0200 Subject: [PATCH 107/166] minor improvements --- .../thingsboard/server/controller/BaseController.java | 4 ---- .../server/controller/DeviceConnectivityController.java | 9 +++++---- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 77aa31df20..68a987a0bc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -113,7 +113,6 @@ import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.ClaimDevicesService; -import org.thingsboard.server.dao.device.DeviceConnectivityService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; @@ -209,9 +208,6 @@ public abstract class BaseController { @Autowired protected DeviceService deviceService; - @Autowired - protected DeviceConnectivityService deviceConnectivityService; - @Autowired protected DeviceProfileService deviceProfileService; diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java index b9b12da17d..04b1b4c522 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java @@ -34,6 +34,7 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.dao.device.DeviceConnectivityService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.system.SystemSecurityService; @@ -46,7 +47,6 @@ import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.PROTOCOL; import static org.thingsboard.server.controller.ControllerConstants.PROTOCOL_PARAM_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.PEM_CERT_FILE_NAME; @@ -57,6 +57,7 @@ import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.PEM_CERT_FI @Slf4j public class DeviceConnectivityController extends BaseController { + private final DeviceConnectivityService deviceConnectivityService; private final SystemSecurityService systemSecurityService; @ApiOperation(value = "Get commands to publish device telemetry (getDevicePublishTelemetryCommands)", @@ -86,11 +87,11 @@ public class DeviceConnectivityController extends BaseController { return deviceConnectivityService.findDevicePublishTelemetryCommands(baseUrl, device); } - @ApiOperation(value = "Download mqtt ssl certificate using file path defined in device.connectivity properties (downloadMqttServerCertificate)", notes = "Download Mqtt server certificate." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Download server certificate using file path defined in device.connectivity properties (downloadServerCertificate)", notes = "Download server certificate.") @RequestMapping(value = "/device-connectivity/{protocol}/certificate/download", method = RequestMethod.GET) @ResponseBody - public ResponseEntity downloadMqttServerCertificate(@ApiParam(value = PROTOCOL_PARAM_DESCRIPTION) - @PathVariable(PROTOCOL) String protocol) throws ThingsboardException, IOException { + public ResponseEntity downloadServerCertificate(@ApiParam(value = PROTOCOL_PARAM_DESCRIPTION) + @PathVariable(PROTOCOL) String protocol) throws ThingsboardException, IOException { checkParameter(PROTOCOL, protocol); var pemCert = checkNotNull(deviceConnectivityService.getPemCertFile(protocol), protocol + " pem cert file is not found!"); diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 18e97ecef1..27c6768980 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1397,7 +1397,7 @@ "device-created-check-connectivity": "Device created. Let's check connectivity!", "loading-check-connectivity-command": "Loading check connectivity commands...", "use-following-instructions": "Use the following instructions for sending telemetry on behalf of the device using shell", - "execute-following-command": "Executive the following command", + "execute-following-command": "Execute the following command", "install-curl-windows": "Starting Windows 10 b17063, cURL is available by default", "install-mqtt-windows": "Use the instructions to download, install, setup and run mosquitto_pub", "install-coap-client": "Use the instructions to download, install, setup and run coap-client", From 49b149d484e99c802715eb81283ab245f2ee25f2 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 28 Jul 2023 18:32:36 +0300 Subject: [PATCH 108/166] UI: Refactoring for new style --- .../lib/multiple-input-widget.component.html | 42 ++++++++++++------- .../lib/multiple-input-widget.component.scss | 26 +++++++++++- .../components/color-input.component.ts | 5 ++- 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index fa51899c4a..9c228ec93f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -28,7 +28,7 @@
- + {{key.label}}
- + {{key.label}}
- + {{key.label}} - + {{key.label}}
- + {{key.label}}
- - + +
+
+ + {{key.settings.icon}} + + icon + + + {{key.label}} +
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss index d0dd324e52..8fec24cf05 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss @@ -21,7 +21,7 @@ flex-direction: column; .tb-multiple-input-container { - padding: 0 8px; + padding: 8px 8px 0; flex: 1 1 100%; overflow-x: hidden; overflow-y: auto; @@ -37,6 +37,30 @@ } } + .color-picker-input { + height: 56px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 7px 16px 7px 12px; + margin: 0 10px 22px 0; + border: 1px solid rgba(0, 0, 0, 0.4); + border-radius: 6px; + + .mat-icon, img { + margin-right: 5px; + } + + .mat-divider-vertical { + height: 56px; + margin-top: -7px; + margin-bottom: -7px; + border-right-color: rgba(0, 0, 0, 0.4); + } + } + .input-field { padding-right: 10px; diff --git a/ui-ngx/src/app/shared/components/color-input.component.ts b/ui-ngx/src/app/shared/components/color-input.component.ts index fa6c73116e..88a49f756e 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.ts +++ b/ui-ngx/src/app/shared/components/color-input.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -91,6 +91,8 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro @Input() disabled: boolean; + @Output() colorChanged: EventEmitter = new EventEmitter(); + private modelValue: string; private propagateChange = null; @@ -150,6 +152,7 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro if (this.modelValue !== color) { this.modelValue = color; this.propagateChange(this.modelValue); + this.colorChanged.emit(color); } } From 08bd89d0bee76a86b51244ef3548a64b5dd6e423 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 28 Jul 2023 18:44:22 +0300 Subject: [PATCH 109/166] UI: Remove divider --- .../widget/lib/multiple-input-widget.component.html | 1 - .../widget/lib/multiple-input-widget.component.scss | 7 ------- 2 files changed, 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 67c28bd878..6c749cab3b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -184,7 +184,6 @@ {{key.label}}
- Date: Mon, 31 Jul 2023 07:56:31 +0300 Subject: [PATCH 110/166] Fix for removing user from sysadmin level alarm unassignment --- .../entitiy/user/DefaultUserService.java | 2 +- .../controller/AlarmControllerTest.java | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java index 0c04e46ff5..d9f11dacb5 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java @@ -82,7 +82,7 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse UserId userId = tbUser.getId(); try { - tbAlarmService.unassignUserAlarms(tenantId, tbUser, System.currentTimeMillis()); + tbAlarmService.unassignUserAlarms(tbUser.getTenantId(), tbUser, System.currentTimeMillis()); userService.deleteUser(tenantId, userId); notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, customerId, userId, tbUser, user, ActionType.DELETED, true, null, customerId.toString()); diff --git a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java index 50761be096..6ce6e22e9a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java @@ -531,6 +531,55 @@ public class AlarmControllerTest extends AbstractControllerTest { tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_UNASSIGNED); } + @Test + public void testUnassignTenantUserAlarmOnUserRemoving() throws Exception { + loginDifferentTenant(); + + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(tenantId); + user.setEmail("tenantForAssign@thingsboard.org"); + User savedUser = createUser(user, "password"); + + Device device = createDevice("Different tenant device", "default", "differentTenantTest"); + + Alarm alarm = Alarm.builder() + .type(TEST_ALARM_TYPE) + .tenantId(savedDifferentTenant.getId()) + .originator(device.getId()) + .severity(AlarmSeverity.MAJOR) + .build(); + alarm = doPost("/api/alarm", alarm, Alarm.class); + Assert.assertNotNull(alarm); + + alarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class); + Assert.assertNotNull(alarm); + + Mockito.reset(tbClusterService, auditLogService); + long beforeAssignmentTs = System.currentTimeMillis(); + + doPost("/api/alarm/" + alarm.getId() + "/assign/" + savedUser.getId().getId()).andExpect(status().isOk()); + AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class); + Assert.assertNotNull(foundAlarm); + Assert.assertEquals(savedUser.getId(), foundAlarm.getAssigneeId()); + Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs); + + beforeAssignmentTs = System.currentTimeMillis(); + + Mockito.reset(tbClusterService, auditLogService); + + loginSysAdmin(); + + doDelete("/api/user/" + savedUser.getId().getId()).andExpect(status().isOk()); + + loginDifferentTenant(); + + foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class); + Assert.assertNotNull(foundAlarm); + Assert.assertNull(foundAlarm.getAssigneeId()); + Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs); + } + @Test public void testUnassignAlarmOnUserRemoving() throws Exception { loginDifferentTenant(); From 037dbd25d07b2a45699d9752b012d3a5f1660625 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Mon, 31 Jul 2023 09:28:08 +0300 Subject: [PATCH 111/166] Enabled test with this message for OUT messages with errors --- .../src/app/modules/home/components/event/event-table-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index 026624dbf3..d574a3593a 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -360,7 +360,7 @@ export class EventTableConfig extends EntityTableConfig { this.cellActionDescriptors.push({ name: this.translate.instant('rulenode.test-with-this-message', {test: this.translate.instant(this.testButtonLabel)}), icon: 'bug_report', - isEnabled: (entity) => entity.body.type === 'IN', + isEnabled: (entity) => entity.body.type === 'IN' || entity.body.error !== undefined, onAction: ($event, entity) => { this.debugEventSelected.next(entity.body); } From 054b1901448f2d48abaeb9ad13d786f027dbbfe2 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 31 Jul 2023 12:25:48 +0300 Subject: [PATCH 112/166] UI: Add routes tab settings replaceUrl --- .../modules/home/components/router-tabs.component.html | 1 + .../modules/home/components/router-tabs.component.ts | 6 ++++++ ui-ngx/src/app/modules/home/home.component.ts | 9 ++++----- .../home/pages/account/account-routing.module.ts | 10 ++++++++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/router-tabs.component.html b/ui-ngx/src/app/modules/home/components/router-tabs.component.html index f16a761c3e..5ad09403c2 100644 --- a/ui-ngx/src/app/modules/home/components/router-tabs.component.html +++ b/ui-ngx/src/app/modules/home/components/router-tabs.component.html @@ -20,6 +20,7 @@
-
-
-
+
+
{{ 'widgets.input-widgets.no-entity-selected' | translate }}
-
+
{{ 'widgets.input-widgets.not-allowed-entity' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss index 7a27c39907..f6edd1fb52 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss @@ -37,17 +37,22 @@ } } - .color-picker-input { - height: 56px; + .tb-multiple-input-layout { display: flex; flex-direction: row; - align-items: center; - justify-content: space-between; - gap: 16px; + align-items: start; + } + + .color-picker-input { padding: 7px 16px 7px 12px; margin: 0 10px 22px 0; - border: 1px solid rgba(0, 0, 0, 0.4); - border-radius: 6px; + border-color: rgba(0, 0, 0, 0.4); + + .label-container { + display: flex; + flex-direction: row; + align-items: center; + } .mat-icon, img { margin-right: 5px; @@ -78,6 +83,30 @@ .vertical-alignment { flex-direction: column; } + + &--buttons-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: end; + &__button { + max-height: 50px; + margin-right:20px; + } + } + + &__errors { + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + &__error { + text-align: center; + font-size: 18px; + color: #a0a0a0; + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index 4c7fb1cfec..6f29a495cf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -390,6 +390,12 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni } }); } + } else if (key.settings.dataKeyValueType === 'color') { + formControl.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { + this.inputChanged(source, key); + }); } this.multipleInputFormGroup.addControl(key.formId, formControl); } diff --git a/ui-ngx/src/app/shared/components/color-input.component.ts b/ui-ngx/src/app/shared/components/color-input.component.ts index 8997409e7c..f22b91fde2 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.ts +++ b/ui-ngx/src/app/shared/components/color-input.component.ts @@ -14,17 +14,7 @@ /// limitations under the License. /// -import { - ChangeDetectorRef, - Component, - EventEmitter, - forwardRef, - Input, - OnInit, - Output, - Renderer2, - ViewContainerRef -} from '@angular/core'; +import { ChangeDetectorRef, Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -110,8 +100,6 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro @Input() disabled: boolean; - @Output() colorChanged: EventEmitter = new EventEmitter(); - private modelValue: string; private propagateChange = null; @@ -174,7 +162,6 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro if (this.modelValue !== color) { this.modelValue = color; this.propagateChange(this.modelValue); - this.colorChanged.emit(color); } } From 1569bee351715f203cb141377050e96d0fd3797c Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 31 Jul 2023 13:34:37 +0300 Subject: [PATCH 116/166] UI: Refactoring error container --- .../widget/lib/multiple-input-widget.component.html | 6 +++--- .../widget/lib/multiple-input-widget.component.scss | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html index 39b135b77a..ae739332be 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.html @@ -207,11 +207,11 @@ {{ saveButtonLabel }}
-
-
+
+
{{ 'widgets.input-widgets.no-entity-selected' | translate }}
-
+
{{ 'widgets.input-widgets.not-allowed-entity' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss index f6edd1fb52..3185bc8b17 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.scss @@ -95,17 +95,17 @@ } } - &__errors { + &--errors-container { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; - } - &__error { - text-align: center; - font-size: 18px; - color: #a0a0a0; + &__error { + text-align: center; + font-size: 18px; + color: #a0a0a0; + } } } } From 68149d96739ed1445f3ad3c25c622ea72dc7810b Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 16 Jun 2023 15:40:29 +0200 Subject: [PATCH 117/166] added recalculetePartitions delay for node restart --- .../src/main/resources/thingsboard.yml | 1 + .../queue/discovery/ZkDiscoveryService.java | 31 ++++++++++++++++++- .../src/main/resources/tb-vc-executor.yml | 1 + .../src/main/resources/tb-coap-transport.yml | 1 + .../src/main/resources/tb-http-transport.yml | 1 + .../src/main/resources/tb-lwm2m-transport.yml | 1 + .../src/main/resources/tb-mqtt-transport.yml | 1 + .../src/main/resources/tb-snmp-transport.yml | 1 + 8 files changed, 37 insertions(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 3666678561..19804c0588 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -96,6 +96,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cluster: stats: diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index fcf80bcf3d..17d046a4cb 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -44,8 +44,10 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.List; import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -66,6 +68,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi private Integer zkSessionTimeout; @Value("${zk.zk_dir}") private String zkDir; + @Value("${zk.recalculate_delay:120000}") + private Long recalculateDelay; + + private final ConcurrentHashMap> delayedTasks; private final TbServiceInfoProvider serviceInfoProvider; private final PartitionService partitionService; @@ -82,6 +88,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi PartitionService partitionService) { this.serviceInfoProvider = serviceInfoProvider; this.partitionService = partitionService; + delayedTasks = new ConcurrentHashMap<>(); } @PostConstruct @@ -290,8 +297,30 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi log.debug("Processing [{}] event for [{}]", pathChildrenCacheEvent.getType(), instance.getServiceId()); switch (pathChildrenCacheEvent.getType()) { case CHILD_ADDED: + ScheduledFuture task = delayedTasks.remove(instance.getServiceId()); + if (task != null) { + if (!task.cancel(false)) { + log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + recalculatePartitions(); + } else { + log.debug("[{}] Recalculate partitions ignored. Service restarted in time [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + } + } else { + log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + recalculatePartitions(); + } + break; case CHILD_REMOVED: - recalculatePartitions(); + ScheduledFuture future = zkExecutorService.schedule(() -> { + log.debug("[{}] Going to recalculate partitions due to removed node [{}]", + instance.getServiceId(), instance.getServiceTypesList()); + delayedTasks.remove(instance.getServiceId()); + recalculatePartitions(); + }, recalculateDelay, TimeUnit.MILLISECONDS); + delayedTasks.put(instance.getServiceId(), future); break; default: break; diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml index 352f94e091..0dbb19a71a 100644 --- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml +++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" queue: type: "${TB_QUEUE_TYPE:kafka}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 7ea553fe5c..c8f4b5a099 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 346ec48eae..fe181f12f2 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -68,6 +68,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 4e8167d89d..d80279f582 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index 1e0b1ebcd4..fcbf542287 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index 9f086bcbc5..0e84d54fce 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -41,6 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" cache: type: "${CACHE_TYPE:redis}" From ce9552e1a8ca44f58a369051bfc9f5bc24ca1477 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 6 Jul 2023 13:31:25 +0200 Subject: [PATCH 118/166] improvements --- .../queue/discovery/ZkDiscoveryService.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 17d046a4cb..24a7863b24 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -299,16 +299,16 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi case CHILD_ADDED: ScheduledFuture task = delayedTasks.remove(instance.getServiceId()); if (task != null) { - if (!task.cancel(false)) { - log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + if (task.cancel(false)) { + log.debug("[{}] Recalculate partitions ignored. Service was restarted in time [{}].", instance.getServiceId(), instance.getServiceTypesList()); - recalculatePartitions(); } else { - log.debug("[{}] Recalculate partitions ignored. Service restarted in time [{}]", + log.debug("[{}] Going to recalculate partitions. Service was not restarted in time [{}]!", instance.getServiceId(), instance.getServiceTypesList()); + recalculatePartitions(); } } else { - log.debug("[{}] Going to recalculate partitions due to adding new node [{}]", + log.debug("[{}] Going to recalculate partitions due to adding new node [{}].", instance.getServiceId(), instance.getServiceTypesList()); recalculatePartitions(); } @@ -317,8 +317,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi ScheduledFuture future = zkExecutorService.schedule(() -> { log.debug("[{}] Going to recalculate partitions due to removed node [{}]", instance.getServiceId(), instance.getServiceTypesList()); - delayedTasks.remove(instance.getServiceId()); - recalculatePartitions(); + ScheduledFuture removedTask = delayedTasks.remove(instance.getServiceId()); + if (removedTask != null) { + recalculatePartitions(); + } }, recalculateDelay, TimeUnit.MILLISECONDS); delayedTasks.put(instance.getServiceId(), future); break; @@ -332,6 +334,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi * Synchronized to ensure that other servers info is up to date * */ synchronized void recalculatePartitions() { + delayedTasks.clear(); partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers()); } From 948f517898ff2207e6ba797e83ca2f77a3194790 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 14 Jul 2023 19:45:23 +0200 Subject: [PATCH 119/166] added zk restart node tests --- .../queue/discovery/ZkDiscoveryService.java | 2 +- .../discovery/ZkDiscoveryServiceTest.java | 173 ++++++++++++++++++ 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 24a7863b24..50378d3387 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -71,7 +71,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi @Value("${zk.recalculate_delay:120000}") private Long recalculateDelay; - private final ConcurrentHashMap> delayedTasks; + protected final ConcurrentHashMap> delayedTasks; private final TbServiceInfoProvider serviceInfoProvider; private final PartitionService partitionService; diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java new file mode 100644 index 0000000000..38cad217aa --- /dev/null +++ b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java @@ -0,0 +1,173 @@ +/** + * 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.queue.discovery; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.imps.CuratorFrameworkState; +import org.apache.curator.framework.recipes.cache.ChildData; +import org.apache.curator.framework.recipes.cache.PathChildrenCache; +import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_ADDED; +import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ZkDiscoveryServiceTest { + + @Mock + private TbServiceInfoProvider serviceInfoProvider; + + @Mock + private PartitionService partitionService; + + @Mock + private CuratorFramework client; + + @Mock + private PathChildrenCache cache; + + private ScheduledExecutorService zkExecutorService; + + @Mock + private CuratorFramework curatorFramework; + + private ZkDiscoveryService zkDiscoveryService; + + @Before + public void setup() { + zkDiscoveryService = Mockito.spy(new ZkDiscoveryService(serviceInfoProvider, partitionService)); + zkExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("zk-discovery")); + when(client.getState()).thenReturn(CuratorFrameworkState.STARTED); + ReflectionTestUtils.setField(zkDiscoveryService, "stopped", false); + ReflectionTestUtils.setField(zkDiscoveryService, "client", client); + ReflectionTestUtils.setField(zkDiscoveryService, "cache", cache); + ReflectionTestUtils.setField(zkDiscoveryService, "nodePath", "/thingsboard/nodes/0000000010"); + ReflectionTestUtils.setField(zkDiscoveryService, "zkExecutorService", zkExecutorService); + ReflectionTestUtils.setField(zkDiscoveryService, "recalculateDelay", 1000L); + ReflectionTestUtils.setField(zkDiscoveryService, "zkDir", "/thingsboard"); + } + + @Test + public void restartNodeTest() throws Exception { + var currentInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("currentId").build(); + var currentData = new ChildData("/thingsboard/nodes/0000000010", null, currentInfo.toByteArray()); + var childInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("childId").build(); + var childData = new ChildData("/thingsboard/nodes/0000000020", null, childInfo.toByteArray()); + + when(serviceInfoProvider.getServiceInfo()).thenReturn(currentInfo); + List dataList = new ArrayList<>(); + dataList.add(currentData); + when(cache.getCurrentData()).thenReturn(dataList); + + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); + + //Restart in timeAssert.assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + stopNode(childData); + + assertEquals(1, zkDiscoveryService.delayedTasks.size()); + + verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + + startNode(childData); + + verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + + Thread.sleep(2000); + + verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + + assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + + //Restart not in time + stopNode(childData); + + assertEquals(1, zkDiscoveryService.delayedTasks.size()); + + Thread.sleep(2000); + + assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(Collections.emptyList())); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); + + //Start another node during restart + var anotherInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("anotherId").build(); + var anotherData = new ChildData("/thingsboard/nodes/0000000030", null, anotherInfo.toByteArray()); + + stopNode(childData); + + assertEquals(1, zkDiscoveryService.delayedTasks.size()); + + startNode(anotherData); + + assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(anotherInfo))); + reset(partitionService); + + Thread.sleep(2000); + + verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(anotherInfo, childInfo))); + } + + private void startNode(ChildData data) throws Exception { + cache.getCurrentData().add(data); + zkDiscoveryService.childEvent(curatorFramework, new PathChildrenCacheEvent(CHILD_ADDED, data)); + } + + private void stopNode(ChildData data) throws Exception { + cache.getCurrentData().remove(data); + zkDiscoveryService.childEvent(curatorFramework, new PathChildrenCacheEvent(CHILD_REMOVED, data)); + } + +} From ac2aac8aa7a264e8ff9452714818cd1dfcc9ba00 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 28 Jul 2023 14:20:33 +0200 Subject: [PATCH 120/166] refactored due to comments --- .../src/main/resources/thingsboard.yml | 2 +- .../queue/discovery/ZkDiscoveryService.java | 26 +++++--- .../discovery/ZkDiscoveryServiceTest.java | 62 ++++++++++++------- .../src/main/resources/tb-vc-executor.yml | 2 +- .../src/main/resources/tb-coap-transport.yml | 2 +- .../src/main/resources/tb-http-transport.yml | 2 +- .../src/main/resources/tb-lwm2m-transport.yml | 2 +- .../src/main/resources/tb-mqtt-transport.yml | 2 +- .../src/main/resources/tb-snmp-transport.yml | 2 +- 9 files changed, 62 insertions(+), 40 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 19804c0588..1f16fbc414 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -96,7 +96,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cluster: stats: diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 50378d3387..44999d016a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.queue.discovery; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.ProtocolStringList; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.curator.framework.CuratorFramework; @@ -68,7 +69,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi private Integer zkSessionTimeout; @Value("${zk.zk_dir}") private String zkDir; - @Value("${zk.recalculate_delay:120000}") + @Value("${zk.recalculate_delay:60000}") private Long recalculateDelay; protected final ConcurrentHashMap> delayedTasks; @@ -294,35 +295,39 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi log.error("Failed to decode server instance for node {}", data.getPath(), e); throw e; } - log.debug("Processing [{}] event for [{}]", pathChildrenCacheEvent.getType(), instance.getServiceId()); + + String serviceId = instance.getServiceId(); + ProtocolStringList serviceTypesList = instance.getServiceTypesList(); + + log.trace("Processing [{}] event for [{}]", pathChildrenCacheEvent.getType(), serviceId); switch (pathChildrenCacheEvent.getType()) { case CHILD_ADDED: - ScheduledFuture task = delayedTasks.remove(instance.getServiceId()); + ScheduledFuture task = delayedTasks.remove(serviceId); if (task != null) { if (task.cancel(false)) { log.debug("[{}] Recalculate partitions ignored. Service was restarted in time [{}].", - instance.getServiceId(), instance.getServiceTypesList()); + serviceId, serviceTypesList); } else { log.debug("[{}] Going to recalculate partitions. Service was not restarted in time [{}]!", - instance.getServiceId(), instance.getServiceTypesList()); + serviceId, serviceTypesList); recalculatePartitions(); } } else { - log.debug("[{}] Going to recalculate partitions due to adding new node [{}].", - instance.getServiceId(), instance.getServiceTypesList()); + log.trace("[{}] Going to recalculate partitions due to adding new node [{}].", + serviceId, serviceTypesList); recalculatePartitions(); } break; case CHILD_REMOVED: ScheduledFuture future = zkExecutorService.schedule(() -> { log.debug("[{}] Going to recalculate partitions due to removed node [{}]", - instance.getServiceId(), instance.getServiceTypesList()); - ScheduledFuture removedTask = delayedTasks.remove(instance.getServiceId()); + serviceId, serviceTypesList); + ScheduledFuture removedTask = delayedTasks.remove(serviceId); if (removedTask != null) { recalculatePartitions(); } }, recalculateDelay, TimeUnit.MILLISECONDS); - delayedTasks.put(instance.getServiceId(), future); + delayedTasks.put(serviceId, future); break; default: break; @@ -334,6 +339,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi * Synchronized to ensure that other servers info is up to date * */ synchronized void recalculatePartitions() { + delayedTasks.values().forEach(future -> future.cancel(false)); delayedTasks.clear(); partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers()); } diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java index 38cad217aa..a8810efd0e 100644 --- a/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java +++ b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/ZkDiscoveryServiceTest.java @@ -63,68 +63,76 @@ public class ZkDiscoveryServiceTest { @Mock private PathChildrenCache cache; - private ScheduledExecutorService zkExecutorService; - @Mock private CuratorFramework curatorFramework; private ZkDiscoveryService zkDiscoveryService; + private static final long RECALCULATE_DELAY = 100L; + + final TransportProtos.ServiceInfo currentInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("tb-rule-engine-0").build(); + final ChildData currentData = new ChildData("/thingsboard/nodes/0000000010", null, currentInfo.toByteArray()); + final TransportProtos.ServiceInfo childInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("tb-rule-engine-1").build(); + final ChildData childData = new ChildData("/thingsboard/nodes/0000000020", null, childInfo.toByteArray()); + @Before public void setup() { zkDiscoveryService = Mockito.spy(new ZkDiscoveryService(serviceInfoProvider, partitionService)); - zkExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("zk-discovery")); + ScheduledExecutorService zkExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("zk-discovery")); when(client.getState()).thenReturn(CuratorFrameworkState.STARTED); ReflectionTestUtils.setField(zkDiscoveryService, "stopped", false); ReflectionTestUtils.setField(zkDiscoveryService, "client", client); ReflectionTestUtils.setField(zkDiscoveryService, "cache", cache); ReflectionTestUtils.setField(zkDiscoveryService, "nodePath", "/thingsboard/nodes/0000000010"); ReflectionTestUtils.setField(zkDiscoveryService, "zkExecutorService", zkExecutorService); - ReflectionTestUtils.setField(zkDiscoveryService, "recalculateDelay", 1000L); + ReflectionTestUtils.setField(zkDiscoveryService, "recalculateDelay", RECALCULATE_DELAY); ReflectionTestUtils.setField(zkDiscoveryService, "zkDir", "/thingsboard"); - } - - @Test - public void restartNodeTest() throws Exception { - var currentInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("currentId").build(); - var currentData = new ChildData("/thingsboard/nodes/0000000010", null, currentInfo.toByteArray()); - var childInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("childId").build(); - var childData = new ChildData("/thingsboard/nodes/0000000020", null, childInfo.toByteArray()); when(serviceInfoProvider.getServiceInfo()).thenReturn(currentInfo); + List dataList = new ArrayList<>(); dataList.add(currentData); when(cache.getCurrentData()).thenReturn(dataList); + } + @Test + public void restartNodeInTimeTest() throws Exception { startNode(childData); verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); reset(partitionService); - //Restart in timeAssert.assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); stopNode(childData); assertEquals(1, zkDiscoveryService.delayedTasks.size()); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); startNode(childData); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); - Thread.sleep(2000); + Thread.sleep(RECALCULATE_DELAY * 2); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); + } + + @Test + public void restartNodeNotInTimeTest() throws Exception { + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); - //Restart not in time stopNode(childData); assertEquals(1, zkDiscoveryService.delayedTasks.size()); - Thread.sleep(2000); + Thread.sleep(RECALCULATE_DELAY * 2); assertTrue(zkDiscoveryService.delayedTasks.isEmpty()); @@ -135,11 +143,19 @@ public class ZkDiscoveryServiceTest { verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); reset(partitionService); + } - //Start another node during restart - var anotherInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("anotherId").build(); + @Test + public void startAnotherNodeDuringRestartTest() throws Exception { + var anotherInfo = TransportProtos.ServiceInfo.newBuilder().setServiceId("tb-transport").build(); var anotherData = new ChildData("/thingsboard/nodes/0000000030", null, anotherInfo.toByteArray()); + startNode(childData); + + verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(childInfo))); + + reset(partitionService); + stopNode(childData); assertEquals(1, zkDiscoveryService.delayedTasks.size()); @@ -151,9 +167,9 @@ public class ZkDiscoveryServiceTest { verify(partitionService, times(1)).recalculatePartitions(eq(currentInfo), eq(List.of(anotherInfo))); reset(partitionService); - Thread.sleep(2000); + Thread.sleep(RECALCULATE_DELAY * 2); - verify(partitionService, never()).recalculatePartitions(eq(currentInfo), any()); + verify(partitionService, never()).recalculatePartitions(any(), any()); startNode(childData); diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml index 0dbb19a71a..66c6b4d3da 100644 --- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml +++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" queue: type: "${TB_QUEUE_TYPE:kafka}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index c8f4b5a099..f4b5e0bc94 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index fe181f12f2..f92da86b99 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -68,7 +68,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index d80279f582..05388473f0 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index fcbf542287..e131788929 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index 0e84d54fce..a7928eb49f 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:120000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" cache: type: "${CACHE_TYPE:redis}" From 20db421a8aefba1109d75524e7814d3cb5dd4199 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 31 Jul 2023 14:19:34 +0300 Subject: [PATCH 121/166] UI: Implement pagination support on overflow for toggle select/header component. --- .../add-widget-dialog.component.html | 2 +- .../dashboard-page.component.html | 2 +- .../components/toggle-header.component.html | 33 +++- .../components/toggle-header.component.scss | 26 +++ .../components/toggle-header.component.ts | 179 +++++++++++++++++- .../components/toggle-select.component.html | 1 + .../components/toggle-select.component.ts | 9 +- 7 files changed, 238 insertions(+), 14 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html index 7a17157b31..7de7acf413 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html @@ -20,7 +20,7 @@

widget.add

: {{data.widgetInfo.widgetName}}
- + {{ 'widget.basic-mode' | translate }} {{ 'widget.advanced-mode' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html index 6432a3f234..b627783d47 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html @@ -360,7 +360,7 @@ [isReadOnly]="true" (closeDetails)="onEditWidgetClosed()">
- + {{ 'widget.basic-mode' | translate }} {{ 'widget.advanced-mode' | translate }} diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.html b/ui-ngx/src/app/shared/components/toggle-header.component.html index d7ed76de90..c2136558e3 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.html +++ b/ui-ngx/src/app/shared/components/toggle-header.component.html @@ -15,14 +15,31 @@ limitations under the License. --> - - {{ option.name }} - + +
+ + {{ option.name }} + +
+ diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.scss b/ui-ngx/src/app/shared/components/toggle-header.component.scss index 6a6785c11b..dd983f3de9 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.scss +++ b/ui-ngx/src/app/shared/components/toggle-header.component.scss @@ -17,8 +17,34 @@ @import "../../../theme"; @import "../../../scss/constants"; +:host { + max-width: 100%; + display: grid; + grid-template-columns: min-content minmax(auto, 1fr) min-content; + .tb-toggle-header-pagination-button { + display: none; + } + &.tb-toggle-header-pagination-controls-enabled { + .tb-toggle-header-pagination-button { + display: block; + } + } + .tb-toggle-container { + display: inline-grid; + grid-column: 2; + overflow: hidden; + &.tb-disable-pagination { + overflow: visible; + } + } + .tb-toggle-header { + transition: transform 500ms cubic-bezier(0.35, 0, 0.25, 1); + } +} + :host ::ng-deep { .mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header { + overflow: visible; width: 100%; border-radius: 100px; height: 32px; diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.ts b/ui-ngx/src/app/shared/components/toggle-header.component.ts index 35daad0e3f..6599a6fe35 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.ts +++ b/ui-ngx/src/app/shared/components/toggle-header.component.ts @@ -15,18 +15,22 @@ /// import { + AfterContentChecked, AfterContentInit, + AfterViewInit, ChangeDetectorRef, Component, ContentChildren, Directive, ElementRef, EventEmitter, + HostBinding, Input, OnDestroy, OnInit, Output, - QueryList + QueryList, + ViewChild } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; @@ -36,6 +40,8 @@ import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { coerceBoolean } from '@shared/decorators/coercion'; import { startWith, takeUntil } from 'rxjs/operators'; +import { Platform } from '@angular/cdk/platform'; +import { MatButtonToggle, MatButtonToggleGroup } from '@angular/material/button-toggle'; export interface ToggleHeaderOption { name: string; @@ -44,6 +50,8 @@ export interface ToggleHeaderOption { export type ToggleHeaderAppearance = 'fill' | 'fill-invert' | 'stroked'; +export type ScrollDirection = 'after' | 'before'; + @Directive( { // eslint-disable-next-line @angular-eslint/directive-selector @@ -72,7 +80,7 @@ export abstract class _ToggleBase extends PageComponent implements AfterContentI @Input() options: ToggleHeaderOption[] = []; - private _destroyed = new Subject(); + protected _destroyed = new Subject(); protected constructor(protected store: Store) { super(store); @@ -109,7 +117,34 @@ export abstract class _ToggleBase extends PageComponent implements AfterContentI templateUrl: './toggle-header.component.html', styleUrls: ['./toggle-header.component.scss'] }) -export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterContentInit, OnDestroy { +export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterViewInit, AfterContentInit, AfterContentChecked, OnDestroy { + + @ViewChild('toggleGroup', {static: false}) + toggleGroup: ElementRef; + + @ViewChild(MatButtonToggleGroup, {static: false}) + buttonToggleGroup: MatButtonToggleGroup; + + @ViewChild('toggleGroupContainer', {static: false}) + toggleGroupContainer: ElementRef; + + @HostBinding('class.tb-toggle-header-pagination-controls-enabled') + private showPaginationControls = false; + + private toggleGroupResize$: ResizeObserver; + + leftPaginationEnabled = false; + rightPaginationEnabled = false; + + private _scrollDistance = 0; + private _scrollDistanceChanged: boolean; + + get scrollDistance(): number { + return this._scrollDistance; + } + set scrollDistance(value: number) { + this._scrollTo(value); + } @Input() value: any; @@ -120,6 +155,10 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterC @Input() name: string; + @Input() + @coerceBoolean() + disablePagination = false; + @Input() @coerceBoolean() useSelectOnMdLg = true; @@ -141,6 +180,7 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterC constructor(protected store: Store, private cd: ChangeDetectorRef, + private platform: Platform, private breakpointObserver: BreakpointObserver) { super(store); } @@ -154,9 +194,142 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterC this.cd.markForCheck(); } ); + if (!this.disablePagination) { + this.valueChange.pipe(takeUntil(this._destroyed)).subscribe(() => { + this.scrollToToggleOptionValue(); + }); + } + } + + ngOnDestroy() { + if (this.toggleGroupResize$) { + this.toggleGroupResize$.disconnect(); + } + super.ngOnDestroy(); + } + + ngAfterViewInit() { + if (!this.disablePagination && !this.useSelectOnMdLg) { + this.toggleGroupResize$ = new ResizeObserver(() => { + this.updatePagination(); + }); + this.toggleGroupResize$.observe(this.toggleGroupContainer.nativeElement); + } + } + + ngAfterContentChecked() { + if (this._scrollDistanceChanged) { + this.updateToggleHeaderScrollPosition(); + this._scrollDistanceChanged = false; + this.cd.markForCheck(); + } } trackByHeaderOption(index: number, option: ToggleHeaderOption){ return option.value; } + + handlePaginatorClick(direction: ScrollDirection, $event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.scrollHeader(direction); + } + + handlePaginatorTouchStart(direction: ScrollDirection, $event: Event) { + if (direction === 'before' && !this.leftPaginationEnabled || + direction === 'after' && !this.rightPaginationEnabled) { + $event.preventDefault(); + } + } + + private scrollHeader(direction: ScrollDirection) { + const viewLength = this.toggleGroup.nativeElement.offsetWidth; + // Move the scroll distance one-third the length of the tab list's viewport. + const scrollAmount = ((direction === 'before' ? -1 : 1) * viewLength) / 3; + return this._scrollTo(this._scrollDistance + scrollAmount); + } + + private scrollToToggleOptionValue() { + if (this.buttonToggleGroup && this.buttonToggleGroup.selected) { + const selectedToggleButton = this.buttonToggleGroup.selected as MatButtonToggle; + const viewLength = this.toggleGroupContainer.nativeElement.offsetWidth; + const {offsetLeft, offsetWidth} = (selectedToggleButton._buttonElement.nativeElement.offsetParent as HTMLElement); + const labelBeforePos = offsetLeft; // this.toggleGroup.nativeElement.offsetWidth - offsetLeft; + const labelAfterPos = labelBeforePos + offsetWidth; + const beforeVisiblePos = this.scrollDistance; + const afterVisiblePos = this.scrollDistance + viewLength; + if (labelBeforePos < beforeVisiblePos) { + this.scrollDistance -= beforeVisiblePos - labelBeforePos; + } else if (labelAfterPos > afterVisiblePos) { + this.scrollDistance += Math.min( + labelAfterPos - afterVisiblePos, + labelBeforePos - beforeVisiblePos, + ); + } + } + } + + private updatePagination() { + this.checkPaginationEnabled(); + this.checkPaginationControls(); + this.updateToggleHeaderScrollPosition(); + } + + private checkPaginationEnabled() { + if (this.toggleGroupContainer) { + const isEnabled = this.toggleGroup.nativeElement.scrollWidth > this.toggleGroupContainer.nativeElement.offsetWidth; + if (isEnabled !== this.showPaginationControls) { + if (!isEnabled) { + this.scrollDistance = 0; + } else { + setTimeout(() => { + this.scrollToToggleOptionValue(); + }, 0); + } + this.cd.markForCheck(); + this.showPaginationControls = isEnabled; + } + } else { + this.showPaginationControls = false; + } + } + + private checkPaginationControls() { + if (!this.showPaginationControls) { + this.leftPaginationEnabled = this.rightPaginationEnabled = false; + } else { + // Check if the pagination arrows should be activated. + this.leftPaginationEnabled = this.scrollDistance > 0; + this.rightPaginationEnabled = this.scrollDistance < this.getMaxScrollDistance(); + this.cd.markForCheck(); + } + } + + private getMaxScrollDistance(): number { + const lengthOfToggleGroup = this.toggleGroup.nativeElement.scrollWidth; + const viewLength = this.toggleGroupContainer.nativeElement.offsetWidth; + return lengthOfToggleGroup - viewLength || 0; + } + + private _scrollTo(position: number) { + if (!this.showPaginationControls) { + return {maxScrollDistance: 0, distance: 0}; + } else { + const maxScrollDistance = this.getMaxScrollDistance(); + this._scrollDistance = Math.max(0, Math.min(maxScrollDistance, position)); + this._scrollDistanceChanged = true; + this.checkPaginationControls(); + return {maxScrollDistance, distance: this._scrollDistance}; + } + } + + private updateToggleHeaderScrollPosition() { + const scrollDistance = this.scrollDistance; + const translateX = -scrollDistance; + this.toggleGroup.nativeElement.style.transform = `translateX(${Math.round(translateX)}px)`; + if (this.platform.TRIDENT || this.platform.EDGE) { + this.toggleGroupContainer.nativeElement.scrollLeft = 0; + } + } } diff --git a/ui-ngx/src/app/shared/components/toggle-select.component.html b/ui-ngx/src/app/shared/components/toggle-select.component.html index a5ce7778b5..819dc76ee4 100644 --- a/ui-ngx/src/app/shared/components/toggle-select.component.html +++ b/ui-ngx/src/app/shared/components/toggle-select.component.html @@ -20,6 +20,7 @@ useSelectOnMdLg="false" [disabled]="disabled" [appearance]="appearance" + [disablePagination]="disablePagination" [options]="options" [value]="modelValue" (valueChange)="updateModel($event)"> diff --git a/ui-ngx/src/app/shared/components/toggle-select.component.ts b/ui-ngx/src/app/shared/components/toggle-select.component.ts index 8338e04f6a..3eee0cf841 100644 --- a/ui-ngx/src/app/shared/components/toggle-select.component.ts +++ b/ui-ngx/src/app/shared/components/toggle-select.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input } from '@angular/core'; +import { Component, forwardRef, HostBinding, Input } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @@ -35,6 +35,9 @@ import { coerceBoolean } from '@shared/decorators/coercion'; }) export class ToggleSelectComponent extends _ToggleBase implements ControlValueAccessor { + @HostBinding('style.maxWidth') + get maxWidth() { return '100%'; } + @Input() @coerceBoolean() disabled: boolean; @@ -42,6 +45,10 @@ export class ToggleSelectComponent extends _ToggleBase implements ControlValueAc @Input() appearance: ToggleHeaderAppearance = 'stroked'; + @Input() + @coerceBoolean() + disablePagination = false; + modelValue: any; private propagateChange = null; From bc43a39643448132ec65f0a35a875a269ea85900 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 31 Jul 2023 16:05:27 +0300 Subject: [PATCH 122/166] UI: Refactoring --- .../entity/entity-select.component.ts | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index 93452e4620..6e235a2dad 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -121,18 +121,12 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte } writeValue(value: EntityId | null): void { - if (value != null) { - this.modelValue = value; - this.entitySelectFormGroup.get('entityType').patchValue(value.entityType, {emitEvent: false}); - this.entitySelectFormGroup.get('entityId').patchValue(value, {emitEvent: false}); - } else { - this.modelValue = { - entityType: this.defaultEntityType, - id: null - }; - this.entitySelectFormGroup.get('entityType').patchValue(this.defaultEntityType, {emitEvent: false}); - this.entitySelectFormGroup.get('entityId').patchValue(null, {emitEvent: false}); - } + this.modelValue = { + entityType: value?.entityType ? value.entityType : this.defaultEntityType, + id: value?.id ? value.id : null + }; + this.entitySelectFormGroup.get('entityType').patchValue(this.modelValue.entityType, {emitEvent: false}); + this.entitySelectFormGroup.get('entityId').patchValue(this.modelValue.id, {emitEvent: false}); } updateView(entityType: EntityType | AliasEntityType | null, entityId: string | null) { @@ -146,6 +140,8 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte || this.modelValue.entityType === AliasEntityType.CURRENT_USER || this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER) { this.modelValue.id = NULL_UUID; + } else if (this.modelValue.entityType === AliasEntityType.CURRENT_CUSTOMER && !this.modelValue.id) { + this.modelValue.id = NULL_UUID; } if (this.modelValue.entityType && this.modelValue.id) { From 3f963107da0454183e89dda9dc868b44ba6ea433 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 31 Jul 2023 16:34:03 +0300 Subject: [PATCH 123/166] UI: Refactoring --- .../components/entity/entity-select.component.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index 6e235a2dad..5190f486ad 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -121,10 +121,17 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte } writeValue(value: EntityId | null): void { - this.modelValue = { - entityType: value?.entityType ? value.entityType : this.defaultEntityType, - id: value?.id ? value.id : null - }; + if (value != null) { + this.modelValue = { + entityType: value.entityType, + id: value.id !== NULL_UUID ? value.id : null + }; + } else { + this.modelValue = { + entityType: value?.entityType ? value.entityType : this.defaultEntityType, + id: null + }; + } this.entitySelectFormGroup.get('entityType').patchValue(this.modelValue.entityType, {emitEvent: false}); this.entitySelectFormGroup.get('entityId').patchValue(this.modelValue.id, {emitEvent: false}); } From 6cee9caad73e7ce1d26b6887d70f9d54616ab1a5 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 31 Jul 2023 17:01:41 +0300 Subject: [PATCH 124/166] UI: Refactoring --- .../src/app/shared/components/entity/entity-select.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index 5190f486ad..4874c6c186 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -128,7 +128,7 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte }; } else { this.modelValue = { - entityType: value?.entityType ? value.entityType : this.defaultEntityType, + entityType: this.defaultEntityType, id: null }; } From 698dfba952ecf9430bd5a43dc104f851b37001d6 Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 1 Aug 2023 14:56:50 +0300 Subject: [PATCH 125/166] tbel: rollback validation switch --- .../script/api/tbel/DefaultTbelInvokeService.java | 7 ------- pom.xml | 2 +- ui-ngx/src/app/shared/models/ace/tbel/worker-tbel.js | 9 ++------- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java index bbf441a659..2a60980f84 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java @@ -66,8 +66,6 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem protected final Map scriptIdToHash = new ConcurrentHashMap<>(); protected final Map scriptMap = new ConcurrentHashMap<>(); - private final String tbelSwitch = "switch"; - private final String tbelSwitchErrorMsg = "TBEL does not support the 'switch'."; protected Cache compiledScriptsCache; private SandboxedParserConfiguration parserConfig; @@ -183,11 +181,6 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem lock.unlock(); } return scriptId; - } catch (CompileException ce) { - if ( ce.getExpr() != null && new String(ce.getExpr()).contains(tbelSwitch)) { - ce = new CompileException(tbelSwitchErrorMsg, ce.getExpr(), ce.getCursor(), ce.getCause()); - } - throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, scriptBody, ce); } catch (Exception e) { throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, scriptBody, e); } diff --git a/pom.xml b/pom.xml index 59d1eff5d3..df8c6ed8d6 100755 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,7 @@ 3.8.1 3.21.9 1.42.1 - 1.0.6 + 1.0.7 1.18.18 1.2.4 1.2.5 diff --git a/ui-ngx/src/app/shared/models/ace/tbel/worker-tbel.js b/ui-ngx/src/app/shared/models/ace/tbel/worker-tbel.js index 3a4b3d90b8..d1d47d0c75 100644 --- a/ui-ngx/src/app/shared/models/ace/tbel/worker-tbel.js +++ b/ui-ngx/src/app/shared/models/ace/tbel/worker-tbel.js @@ -5229,10 +5229,6 @@ var JSHINT = (function() { var a = [], p; while (!state.tokens.next.reach && state.tokens.next.id !== "(end)") { - if (state.tokens.next.value === "switch") { - warning("E067", state.tokens.next, "switch"); - break; - } if (state.tokens.next.id === ";") { p = peek(); @@ -9219,7 +9215,7 @@ var JSHINT = (function() { statements(0); } - if (state.tokens.next.id !== "(end)"&& state.tokens.next.value !== "switch") { + if (state.tokens.next.id !== "(end)") { quit("E041", state.tokens.curr); } @@ -11270,8 +11266,7 @@ var errors = { E064: "Super call may only be used within class method bodies.", E065: "Functions defined outside of strict mode with non-simple parameter lists may not " + "enable strict mode.", - E066: "Asynchronous iteration is only available with for-of loops.", - E067: "Expected an 'if/else' and instead saw 'switch'. TBEL does not support the 'switch' statement." + E066: "Asynchronous iteration is only available with for-of loops." }; var warnings = { From 165b1068bccb932e03e995187bcbe52cdbc335cb Mon Sep 17 00:00:00 2001 From: rusikv Date: Tue, 1 Aug 2023 17:54:39 +0300 Subject: [PATCH 126/166] Refactoring --- .../attribute/attribute-table.component.html | 12 +-- .../attribute/attribute-table.component.ts | 16 +++- .../delete-timeseries-panel.component.html | 16 ++-- .../delete-timeseries-panel.component.scss | 2 +- .../delete-timeseries-panel.component.ts | 90 +++++++++++++------ 5 files changed, 87 insertions(+), 49 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index 95ade10645..788aeeaabc 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -85,20 +85,12 @@ 'attribute.selected-telemetry' : 'attribute.selected-attributes') | translate:{count: dataSource.selection.selected.length} }} - -
-
+ diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss index c0f26644d5..d223b29b47 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss @@ -22,7 +22,7 @@ } :host ::ng-deep{ - div .mat-toolbar { + form .mat-toolbar { background: none; } } diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts index 364ecd2c9a..96eaa5aa1b 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts @@ -14,13 +14,15 @@ /// limitations under the License. /// -import { Component, Inject, InjectionToken, OnInit } from '@angular/core'; +import { Component, Inject, InjectionToken, OnDestroy, OnInit } from '@angular/core'; import { OverlayRef } from '@angular/cdk/overlay'; import { TimeseriesDeleteStrategy, timeseriesDeleteStrategyTranslations } from '@shared/models/telemetry/telemetry.models'; import { MINUTE } from '@shared/models/time/time.models'; +import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Subscription } from 'rxjs'; export const DELETE_TIMESERIES_PANEL_DATA = new InjectionToken('DeleteTimeseriesPanelData'); @@ -33,17 +35,15 @@ export interface DeleteTimeseriesPanelData { templateUrl: './delete-timeseries-panel.component.html', styleUrls: ['./delete-timeseries-panel.component.scss'] }) -export class DeleteTimeseriesPanelComponent implements OnInit { +export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { - strategy: string = TimeseriesDeleteStrategy.DELETE_ALL_DATA; + deleteTimeseriesFormGroup: UntypedFormGroup; - result: string = null; - - startDateTime: Date; + startDateTimeSubscription: Subscription; - endDateTime: Date; + endDateTimeSubscription: Subscription; - rewriteLatestIfDeleted: boolean = true; + result: string = null; strategiesTranslationsMap = timeseriesDeleteStrategyTranslations; @@ -53,22 +53,40 @@ export class DeleteTimeseriesPanelComponent implements OnInit { ]; constructor(@Inject(DELETE_TIMESERIES_PANEL_DATA) public data: DeleteTimeseriesPanelData, - public overlayRef: OverlayRef) { } + public overlayRef: OverlayRef, + public fb: UntypedFormBuilder) { } ngOnInit(): void { - let today = new Date(); - this.startDateTime = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()); - this.endDateTime = today; + const today = new Date(); if (this.data.isMultipleDeletion) { this.strategiesTranslationsMap = new Map(Array.from(this.strategiesTranslationsMap.entries()) .filter(([strategy]) => { return this.multipleDeletionStrategies.includes(strategy); })) } + this.deleteTimeseriesFormGroup = this.fb.group({ + strategy: [TimeseriesDeleteStrategy.DELETE_ALL_DATA], + startDateTime: [new Date(today.getFullYear(), today.getMonth() - 1, today.getDate())], + endDateTime: [today], + rewriteLatest: [true] + }) + this.startDateTimeSubscription = this.getStartDateTimeFormControl().valueChanges.subscribe( + value => this.onStartDateTimeChange(value) + ) + this.endDateTimeSubscription = this.getEndDateTimeFormControl().valueChanges.subscribe( + value => this.onEndDateTimeChange(value) + ) + } + + ngOnDestroy(): void { + this.startDateTimeSubscription.unsubscribe(); + this.startDateTimeSubscription = null; + this.endDateTimeSubscription.unsubscribe(); + this.endDateTimeSubscription = null; } delete(): void { - this.result = this.strategy; + this.result = this.getStrategyFormControl().value; this.overlayRef.dispose(); } @@ -77,28 +95,50 @@ export class DeleteTimeseriesPanelComponent implements OnInit { } isPeriodStrategy(): boolean { - return this.strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD; + return this.getStrategyFormControl().value === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD; } isDeleteLatestStrategy(): boolean { - return this.strategy === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE; + return this.getStrategyFormControl().value === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE; + } + + getStrategyFormControl(): AbstractControl { + return this.deleteTimeseriesFormGroup.get('strategy'); + } + + getStartDateTimeFormControl(): AbstractControl { + return this.deleteTimeseriesFormGroup.get('startDateTime'); + } + + getEndDateTimeFormControl(): AbstractControl { + return this.deleteTimeseriesFormGroup.get('endDateTime'); + } + + getRewriteLatestFormControl(): AbstractControl { + return this.deleteTimeseriesFormGroup.get('rewriteLatest'); } onStartDateTimeChange(newStartDateTime: Date) { - const endDateTimeTs = this.endDateTime.getTime(); - if (newStartDateTime.getTime() >= endDateTimeTs) { - this.startDateTime = new Date(endDateTimeTs - MINUTE); - } else { - this.startDateTime = newStartDateTime; + if (newStartDateTime) { + const endDateTimeTs = this.deleteTimeseriesFormGroup.get('endDateTime').value.getTime(); + const startDateTimeControl = this.getStartDateTimeFormControl(); + if (newStartDateTime.getTime() >= endDateTimeTs) { + startDateTimeControl.patchValue(new Date(endDateTimeTs - MINUTE), {onlySelf: true, emitEvent: false}); + } else { + startDateTimeControl.patchValue(newStartDateTime, {onlySelf: true, emitEvent: false}); + } } } onEndDateTimeChange(newEndDateTime: Date) { - const startDateTimeTs = this.startDateTime.getTime(); - if (newEndDateTime.getTime() <= startDateTimeTs) { - this.endDateTime = new Date(startDateTimeTs + MINUTE); - } else { - this.endDateTime = newEndDateTime; + if (newEndDateTime) { + const startDateTimeTs = this.deleteTimeseriesFormGroup.get('startDateTime').value.getTime(); + const endDateTimeControl = this.getEndDateTimeFormControl(); + if (newEndDateTime.getTime() <= startDateTimeTs) { + endDateTimeControl.patchValue(new Date(startDateTimeTs + MINUTE), {onlySelf: true, emitEvent: false}); + } else { + endDateTimeControl.patchValue(newEndDateTime, {onlySelf: true, emitEvent: false}); + } } } } From bd24bb7335f4501a46ff016494a3f148cb3435e7 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 1 Aug 2023 19:58:59 +0300 Subject: [PATCH 127/166] Widgets UI config: Responsive layout improvements. --- .../add-widget-dialog.component.html | 18 +++-- .../add-widget-dialog.component.scss | 9 +++ .../dashboard-page.component.html | 25 +++++-- .../dashboard-page.component.ts | 2 +- .../dashboard-toolbar.component.scss | 37 ++++++++++ .../dashboard-page/edit-widget.component.html | 44 +++++++---- .../dashboard-page/edit-widget.component.scss | 2 +- .../components/details-panel.component.html | 2 +- .../components/details-panel.component.scss | 13 ++-- .../alarms-table-basic-config.component.html | 2 +- .../widget/config/basic/basic-config.scss | 6 ++ ...entities-table-basic-config.component.html | 2 +- .../simple-card-basic-config.component.html | 2 +- ...meseries-table-basic-config.component.html | 2 +- .../value-card-basic-config.component.html | 2 +- .../chart/flot-basic-config.component.html | 2 +- .../basic/common/data-key-row.component.html | 3 +- .../basic/common/data-key-row.component.scss | 30 +++++++- .../common/data-keys-panel.component.html | 3 +- .../common/data-keys-panel.component.scss | 40 ++++++++-- .../timewindow-config-panel.component.html | 11 +-- .../common/legend-config.component.html | 2 +- .../widget/lib/settings/widget-settings.scss | 3 + .../widget/widget-config.component.html | 22 +++--- .../widget/widget-config.component.scss | 35 +++++++-- .../components/time/timewindow.component.scss | 3 + .../components/time/timewindow.component.ts | 7 +- .../components/toggle-header.component.html | 8 +- .../components/toggle-header.component.scss | 3 - .../components/toggle-header.component.ts | 74 +++++++++++++++---- .../components/toggle-select.component.html | 1 + .../components/toggle-select.component.ts | 3 + ui-ngx/src/form.scss | 66 ++++++++++++----- ui-ngx/src/styles.scss | 29 +++++++- 34 files changed, 393 insertions(+), 120 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html index 7de7acf413..47f8634462 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html @@ -17,14 +17,16 @@ -->
-

widget.add

- : {{data.widgetInfo.widgetName}} -
- - {{ 'widget.basic-mode' | translate }} - {{ 'widget.advanced-mode' | translate }} - -
+
+

{{'widget.add' | translate}}: {{data.widgetInfo.widgetName}}

+
+ + {{ 'widget.basic-mode' | translate }} + {{ 'widget.advanced-mode' | translate }} + +
+
+ + + + @@ -360,7 +371,7 @@ [isReadOnly]="true" (closeDetails)="onEditWidgetClosed()">
- + {{ 'widget.basic-mode' | translate }} {{ 'widget.advanced-mode' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index cead69ab46..3996c1abaf 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -191,7 +191,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC } get hideToolbar(): boolean { - return (this.hideToolbarValue || this.hideToolbarSetting()) && !this.isEdit; + return ((this.hideToolbarValue || this.hideToolbarSetting()) && !this.isEdit) || (this.isEditingWidget || this.isAddingWidget); } @Input() diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss index 0d9ade9bf6..e43132c761 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss @@ -126,6 +126,7 @@ tb-dashboard-toolbar { @media #{$mat-lt-md} { height: $mobile-toolbar-height; max-height: $mobile-toolbar-height; + padding: 0 8px !important; } .close-action { @@ -150,8 +151,44 @@ tb-dashboard-toolbar { .tb-dashboard-action-panel { min-width: 0; height: $half-mobile-toolbar-height; + flex: 1 0 auto; + display: flex; + flex-direction: row-reverse; + place-content: center space-between; + align-items: center; + &.tb-left-panel { + flex: 1 1 auto; + } + + @media #{$mat-lt-md} { + padding-left: 12px; + } + + @media #{$mat-xs} { + gap: 3px; + padding-left: 0; + &.tb-left-panel { + padding-left: 12px; + } + } + + @media #{$mat-sm} { + gap: 6px; + } + + @media #{$mat-md} { + gap: 6px; + } + + @media #{$mat-gt-md} { + gap: 12px; + } @media #{$mat-gt-sm} { + place-content: center flex-start; + &.tb-left-panel { + place-content: center flex-end; + } height: 46px; } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html index a1c8ee6ade..86f069239c 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html @@ -32,26 +32,38 @@ chevron_left {{ 'action.back' | translate }} -
-
- - -
+
+ +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.scss index c31f1d5791..9c4e20a7a5 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.scss @@ -16,7 +16,7 @@ :host { .widget-preview-background { position: absolute; - top: 72px; + top: 68px; left: 0; right: 0; bottom: 0; diff --git a/ui-ngx/src/app/modules/home/components/details-panel.component.html b/ui-ngx/src/app/modules/home/components/details-panel.component.html index 15df7e99e5..748723197f 100644 --- a/ui-ngx/src/app/modules/home/components/details-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/details-panel.component.html @@ -21,7 +21,7 @@
- {{ headerTitle }} + {{ headerTitle }}
{{ headerSubtitle }} diff --git a/ui-ngx/src/app/modules/home/components/details-panel.component.scss b/ui-ngx/src/app/modules/home/components/details-panel.component.scss index 9002246841..451795a2d1 100644 --- a/ui-ngx/src/app/modules/home/components/details-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/details-panel.component.scss @@ -32,16 +32,14 @@ max-height: 120px; &.tb-details-title-header { min-width: 0; + padding: 0 16px 0 8px; } } .tb-details-title { width: inherit; margin: 20px 8px 0 0; - overflow: hidden; font-size: 1rem; font-weight: 400; - text-overflow: ellipsis; - white-space: nowrap; @media #{$mat-gt-sm} { font-size: 1.5rem; @@ -49,13 +47,16 @@ } .tb-details-subtitle { - width: inherit; margin: 10px 0; - overflow: hidden; font-size: 1rem; + opacity: .8; + } + + .tb-details-title-text, .tb-details-subtitle { + width: inherit; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - opacity: .8; } tb-dashboard { diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html index 18cd34609e..ffddc9df59 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html @@ -72,7 +72,7 @@
-
+
widget-config.show-card-buttons
{{ 'action.search' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-config.scss b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-config.scss index d29594fce3..0f88b8f1dc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-config.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-config.scss @@ -13,8 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +@import '../../../../../../../scss/constants'; + :host { display: flex; flex-direction: column; gap: 16px; + @media #{$mat-xs} { + gap: 8px; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html index c5501ae830..c68caab86e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html @@ -61,7 +61,7 @@
-
+
widget-config.show-card-buttons
{{ 'action.search' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.html index 142f32cd4e..92bd2e44ab 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.html @@ -61,7 +61,7 @@
-
+
widget-config.show-card-buttons
{{ 'fullscreen.fullscreen' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html index bab6437485..49196fac3a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html @@ -61,7 +61,7 @@
-
+
widget-config.show-card-buttons
{{ 'action.search' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html index 51bb854826..788c997740 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html @@ -105,7 +105,7 @@
-
+
widget-config.show-card-buttons
{{ 'fullscreen.fullscreen' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html index 1439f74931..952b3f9031 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html @@ -61,7 +61,7 @@
-
+
widget-config.show-card-buttons
{{ 'fullscreen.fullscreen' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html index 22c4c2aace..62f34f3d28 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html @@ -158,7 +158,8 @@
-
-
+
legend.show-values
{{ 'legend.min-option' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.scss index 1971b02b6c..ed74372105 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.scss @@ -19,6 +19,9 @@ display: flex; flex-direction: column; gap: 16px; + @media #{$mat-xs} { + gap: 8px; + } .tb-widget-settings { .fields-group { diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html index 07f655c768..c9dd0732be 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html @@ -20,8 +20,10 @@ - - +
+ + +
@@ -48,18 +50,18 @@
-
+
{{ 'widget-config.display-icon' | translate }}
+ + + - - - @@ -247,7 +249,7 @@
widget-config.limits
-
+
widget-config.data-page-size
@@ -258,19 +260,19 @@
widget-config.data-settings
-
+
widget-config.units
-
+
widget-config.decimals
-
+
widget-config.no-data-display-message
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss index 91b3368ad0..701bfec099 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss @@ -20,16 +20,36 @@ .tb-widget-config { display: flex; flex-direction: column; - gap: 16px; + gap: 8px; .tb-widget-config-header { - padding: 24px 24px 8px; - height: 56px; + padding: 24px 24px 0; display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; + gap: 12px; + flex-direction: column-reverse; + align-items: flex-start; + @media #{$mat-gt-sm} { + gap: 0; + flex-direction: row; + align-items: center; + justify-content: space-between; + } + .tb-widget-config-header-components { + width: 100%; + flex: 1; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + } } .tb-widget-config-content { + & > .mat-content { + padding-top: 8px; + @media #{$mat-xs} { + padding-left: 8px; + padding-right: 8px; + } + } flex: 1; overflow: auto; & > div { @@ -39,6 +59,9 @@ display: flex; flex-direction: column; gap: 16px; + @media #{$mat-xs} { + gap: 8px; + } } } .tb-basic-mode-directive-error { diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.scss b/ui-ngx/src/app/shared/components/time/timewindow.component.scss index 695362197d..af3feec6eb 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.scss @@ -17,6 +17,9 @@ min-width: 48px; margin: 8px 0; max-width: 100%; + &.no-margin { + margin: 0; + } .mdc-button { max-width: 100%; } diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.ts b/ui-ngx/src/app/shared/components/time/timewindow.component.ts index f9b40daac1..ff39e54fc7 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.ts @@ -18,7 +18,7 @@ import { ChangeDetectorRef, Component, ElementRef, - forwardRef, + forwardRef, HostBinding, Injector, Input, StaticProvider, @@ -83,6 +83,11 @@ export class TimewindowComponent implements ControlValueAccessor { return this.historyOnlyValue; } + @HostBinding('class.no-margin') + @Input() + @coerceBoolean() + noMargin = false; + @Input() @coerceBoolean() forAllTimeEnabled = false; diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.html b/ui-ngx/src/app/shared/components/toggle-header.component.html index c2136558e3..7aa391b3b4 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.html +++ b/ui-ngx/src/app/shared/components/toggle-header.component.html @@ -16,13 +16,14 @@ --> -
+
+ class="tb-toggle-header-pagination-button" [class]="{'tb-mat-32': !isMdLg, 'tb-mat-24': isMdLg}"> chevron_right diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.scss b/ui-ngx/src/app/shared/components/toggle-header.component.scss index dd983f3de9..7a41032961 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.scss +++ b/ui-ngx/src/app/shared/components/toggle-header.component.scss @@ -178,9 +178,6 @@ line-height: 16px; letter-spacing: 0.25px; } - .mat-mdc-select-value { - color: rgba(0, 0, 0, 0.38); - } .mat-mdc-select-arrow-wrapper { height: 12px; padding-left: 6px; diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.ts b/ui-ngx/src/app/shared/components/toggle-header.component.ts index 6599a6fe35..a7bddfc7b4 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.ts +++ b/ui-ngx/src/app/shared/components/toggle-header.component.ts @@ -35,7 +35,7 @@ import { import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { Subject, Subscription } from 'rxjs'; +import { BehaviorSubject, Subject, Subscription } from 'rxjs'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { coerceBoolean } from '@shared/decorators/coercion'; @@ -159,9 +159,20 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV @coerceBoolean() disablePagination = false; + @Input() + selectMediaBreakpoint = 'md-lg'; + @Input() @coerceBoolean() - useSelectOnMdLg = true; + set useSelectOnMdLg(value: boolean) { + if (value) { + this.selectMediaBreakpoint = 'md-lg'; + } else { + if (this.selectMediaBreakpoint === 'md-lg') { + this.selectMediaBreakpoint = ''; + } + } + } @Input() @coerceBoolean() @@ -174,7 +185,14 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV @coerceBoolean() disabled = false; - isMdLg: boolean; + get isMdLg(): boolean { + return !this.ignoreMdLgSize && this.isMdLgValue; + } + + private isMdLgValue: boolean; + private useSelectSubject = new BehaviorSubject(false); + + useSelect$ = this.useSelectSubject.asObservable(); private observeBreakpointSubscription: Subscription; @@ -186,11 +204,19 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV } ngOnInit() { - this.isMdLg = this.breakpointObserver.isMatched(MediaBreakpoints['md-lg']); + const mediaBreakpoints = [MediaBreakpoints['md-lg']]; + if (this.selectMediaBreakpoint && this.selectMediaBreakpoint !== 'md-lg') { + mediaBreakpoints.push(MediaBreakpoints[this.selectMediaBreakpoint]); + } this.observeBreakpointSubscription = this.breakpointObserver - .observe(MediaBreakpoints['md-lg']) + .observe(mediaBreakpoints) .subscribe((state: BreakpointState) => { - this.isMdLg = state.matches; + this.isMdLgValue = state.breakpoints[MediaBreakpoints['md-lg']]; + if (this.selectMediaBreakpoint) { + this.useSelectSubject.next(state.breakpoints[MediaBreakpoints[this.selectMediaBreakpoint]]); + } else { + this.useSelectSubject.next(false); + } this.cd.markForCheck(); } ); @@ -202,18 +228,21 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV } ngOnDestroy() { - if (this.toggleGroupResize$) { - this.toggleGroupResize$.disconnect(); - } + this.stopObservePagination(); super.ngOnDestroy(); } ngAfterViewInit() { - if (!this.disablePagination && !this.useSelectOnMdLg) { - this.toggleGroupResize$ = new ResizeObserver(() => { - this.updatePagination(); + if (!this.disablePagination) { + this.useSelect$.pipe(takeUntil(this._destroyed)).subscribe((useSelect) => { + if (useSelect) { + this.removePagination(); + } else { + setTimeout(() => { + this.startObservePagination(); + }, 0); + } }); - this.toggleGroupResize$.observe(this.toggleGroupContainer.nativeElement); } } @@ -243,6 +272,25 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV } } + private startObservePagination() { + this.toggleGroupResize$ = new ResizeObserver(() => { + this.updatePagination(); + }); + this.toggleGroupResize$.observe(this.toggleGroupContainer.nativeElement); + } + + private removePagination() { + this.stopObservePagination(); + this.showPaginationControls = false; + } + + private stopObservePagination() { + if (this.toggleGroupResize$) { + this.toggleGroupResize$.disconnect(); + this.toggleGroupResize$ = null; + } + } + private scrollHeader(direction: ScrollDirection) { const viewLength = this.toggleGroup.nativeElement.offsetWidth; // Move the scroll distance one-third the length of the tab list's viewport. diff --git a/ui-ngx/src/app/shared/components/toggle-select.component.html b/ui-ngx/src/app/shared/components/toggle-select.component.html index 819dc76ee4..c03e0cfcbf 100644 --- a/ui-ngx/src/app/shared/components/toggle-select.component.html +++ b/ui-ngx/src/app/shared/components/toggle-select.component.html @@ -21,6 +21,7 @@ [disabled]="disabled" [appearance]="appearance" [disablePagination]="disablePagination" + [selectMediaBreakpoint]="selectMediaBreakpoint" [options]="options" [value]="modelValue" (valueChange)="updateModel($event)"> diff --git a/ui-ngx/src/app/shared/components/toggle-select.component.ts b/ui-ngx/src/app/shared/components/toggle-select.component.ts index 3eee0cf841..c1541d1128 100644 --- a/ui-ngx/src/app/shared/components/toggle-select.component.ts +++ b/ui-ngx/src/app/shared/components/toggle-select.component.ts @@ -42,6 +42,9 @@ export class ToggleSelectComponent extends _ToggleBase implements ControlValueAc @coerceBoolean() disabled: boolean; + @Input() + selectMediaBreakpoint; + @Input() appearance: ToggleHeaderAppearance = 'stroked'; diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 4a4c018549..92feca1e8e 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -16,6 +16,15 @@ @import './scss/constants'; +@mixin form-row-column($breakpoint) { + @media #{$breakpoint} { + flex-direction: column; + align-items: stretch; + gap: 12px; + padding: 12px 12px 12px 16px; + } +} + .tb-default, .tb-dark { .tb-form-panel { box-shadow: 0 0 10px 6px rgba(11, 17, 51, 0.04); @@ -27,6 +36,10 @@ color: rgba(0, 0, 0, 0.87); letter-spacing: 0.15px; position: relative; + @media #{$mat-xs} { + padding: 12px; + gap: 8px; + } &.no-padding-bottom { padding-bottom: 0; } @@ -52,7 +65,6 @@ > .mat-expansion-panel { padding: 16px; .mat-expansion-panel-header { - height: 32px; .mat-slide { margin: 0; } @@ -66,6 +78,7 @@ overflow: visible; } > .mat-expansion-panel-header { + height: fit-content; user-select: none; font-weight: 500; font-size: 16px; @@ -98,6 +111,10 @@ flex-direction: column; gap: 16px; padding: 16px 0 0 !important; + @media #{$mat-xs} { + padding: 12px 0 0 !important; + gap: 8px; + } } } .tb-json-object-panel, .tb-css-content-panel { @@ -139,6 +156,14 @@ padding: 7px 7px 7px 16px; border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 6px; + &.column { + &-xs { + @include form-row-column($mat-xs) + } + &-lt-md { + @include form-row-column($mat-lt-md) + } + } &.no-border { border: none; border-radius: 0; @@ -360,12 +385,14 @@ } .tb-form-table-row { - height: 38px; display: flex; flex-direction: row; - gap: 12px; - padding-left: 12px; - + gap: 8px; + padding-left: 8px; + @media #{$mat-gt-md} { + gap: 12px; + padding-left: 12px; + } &.tb-draggable { gap: 0; padding-left: 0; @@ -376,12 +403,7 @@ display: flex; flex-direction: row; button.mat-mdc-icon-button.mat-mdc-button-base { - padding: 7px; - width: 38px; - height: 38px; - .mat-icon { - color: rgba(0, 0, 0, 0.38); - } + color: rgba(0, 0, 0, 0.38); &.tb-hidden { visibility: hidden; } @@ -434,21 +456,18 @@ } } - button.mat-mdc-button-base.tb-box-button { + button.mat-mdc-button-base.tb-box-button, .tb-form-table-row-cell-buttons button.mat-mdc-icon-button.mat-mdc-button-base { width: 40px; min-width: 40px; height: 40px; - padding: 7px; + padding: 8px; + &.mat-mdc-outlined-button { + padding: 7px; + } .mat-mdc-button-touch-target { width: 40px; height: 40px; } - &:not(:disabled) { - color: rgba(0, 0, 0, 0.54); - } - &:disabled { - color: rgba(0, 0, 0, 0.12); - } > .mat-icon { width: 24px; height: 24px; @@ -456,4 +475,13 @@ margin: 0; } } + + button.mat-mdc-button-base.tb-box-button { + &:not(:disabled) { + color: rgba(0, 0, 0, 0.54); + } + &:disabled { + color: rgba(0, 0, 0, 0.12); + } + } } diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 75fc0845cf..ec6060823d 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -625,9 +625,36 @@ mat-label { color: white; } } - .mat-mdc-select-value, .mat-mdc-select-arrow { + .mat-mdc-select-value, .mat-mdc-select-arrow, .mat-mdc-select-arrow:after { color: white; } + .mat-mdc-text-field-wrapper { + &.mdc-text-field--outlined { + &:not(.mdc-text-field--focused):not(.mdc-text-field--disabled):not(.mdc-text-field--invalid) { + &:not(:hover) { + .mdc-notched-outline { + .mdc-notched-outline__leading, .mdc-notched-outline__trailing { + border-color: white; + } + } + } + &:hover { + .mdc-notched-outline { + .mdc-notched-outline__leading, .mdc-notched-outline__trailing { + border-color: rgba(255, 255, 255, 0.87); + } + } + } + } + &:not(.mdc-text-field--disabled).mdc-text-field--focused { + .mdc-notched-outline { + .mdc-notched-outline__leading, .mdc-notched-outline__trailing { + border-color: rgba(255, 255, 255, 0.67); + } + } + } + } + } } .mat-toolbar.mat-mdc-table-toolbar { From 12c8903ff559ed96ab741a0335e860cc3c62381f Mon Sep 17 00:00:00 2001 From: rusikv Date: Wed, 2 Aug 2023 12:48:19 +0300 Subject: [PATCH 128/166] Delete timeseries panel form is builded by FormBuilder and is now FormGroup, result of panel is now in result field --- .../attribute/attribute-table.component.html | 2 +- .../attribute/attribute-table.component.ts | 19 ++++---- .../delete-timeseries-panel.component.ts | 44 ++++++++++--------- 3 files changed, 35 insertions(+), 30 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index 788aeeaabc..10a5b03dba 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -85,7 +85,7 @@ 'attribute.selected-telemetry' : 'attribute.selected-attributes') | translate:{count: dataSource.selection.selected.length} }} - @@ -41,13 +41,13 @@ attribute.delete-timeseries.start-time - + attribute.delete-timeseries.ends-on - +
@@ -57,18 +57,18 @@
-
- - - -
- +
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts index 04db1ee223..8adb9bcd5a 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts @@ -21,8 +21,8 @@ import { timeseriesDeleteStrategyTranslations } from '@shared/models/telemetry/telemetry.models'; import { MINUTE } from '@shared/models/time/time.models'; -import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms'; -import { Subject, Subscription } from 'rxjs'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; export const DELETE_TIMESERIES_PANEL_DATA = new InjectionToken('DeleteTimeseriesPanelData'); @@ -47,10 +47,6 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { deleteTimeseriesFormGroup: FormGroup; - startDateTimeSubscription: Subscription; - - endDateTimeSubscription: Subscription; - result: DeleteTimeseriesPanelResult = null; strategiesTranslationsMap = timeseriesDeleteStrategyTranslations; @@ -76,14 +72,28 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { } this.deleteTimeseriesFormGroup = this.fb.group({ strategy: [TimeseriesDeleteStrategy.DELETE_ALL_DATA], - startDateTime: [new Date(today.getFullYear(), today.getMonth() - 1, today.getDate())], - endDateTime: [today], + startDateTime: [ + { value: new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()), disabled: true }, + [Validators.required] + ], + endDateTime: [{ value: today, disabled: true }, [Validators.required]], rewriteLatest: [true] }) - this.startDateTimeSubscription = this.getStartDateTimeFormControl().valueChanges.pipe( + this.deleteTimeseriesFormGroup.get('strategy').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(value => { + if (value === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD) { + this.deleteTimeseriesFormGroup.get('startDateTime').enable({onlySelf: true, emitEvent: false}); + this.deleteTimeseriesFormGroup.get('endDateTime').enable({onlySelf: true, emitEvent: false}); + } else { + this.deleteTimeseriesFormGroup.get('startDateTime').disable({onlySelf: true, emitEvent: false}); + this.deleteTimeseriesFormGroup.get('endDateTime').disable({onlySelf: true, emitEvent: false}); + } + }) + this.deleteTimeseriesFormGroup.get('startDateTime').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe(value => this.onStartDateTimeChange(value)); - this.endDateTimeSubscription = this.getEndDateTimeFormControl().valueChanges.pipe( + this.deleteTimeseriesFormGroup.get('endDateTime').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe(value => this.onEndDateTimeChange(value)); } @@ -94,8 +104,12 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { } delete(): void { - this.result = this.deleteTimeseriesFormGroup.value; - this.overlayRef.dispose(); + if (this.deleteTimeseriesFormGroup.valid) { + this.result = this.deleteTimeseriesFormGroup.value; + this.overlayRef.dispose(); + } else { + this.deleteTimeseriesFormGroup.markAllAsTouched(); + } } cancel(): void { @@ -103,33 +117,22 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { } isPeriodStrategy(): boolean { - return this.getStrategyFormControl().value === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD; + return this.deleteTimeseriesFormGroup.get('strategy').value === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD; } isDeleteLatestStrategy(): boolean { - return this.getStrategyFormControl().value === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE; - } - - getStrategyFormControl(): AbstractControl { - return this.deleteTimeseriesFormGroup.get('strategy'); - } - - getStartDateTimeFormControl(): AbstractControl { - return this.deleteTimeseriesFormGroup.get('startDateTime'); - } - - getEndDateTimeFormControl(): AbstractControl { - return this.deleteTimeseriesFormGroup.get('endDateTime'); + return this.deleteTimeseriesFormGroup.get('strategy').value === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE; } onStartDateTimeChange(newStartDateTime: Date) { if (newStartDateTime) { const endDateTimeTs = this.deleteTimeseriesFormGroup.get('endDateTime').value.getTime(); - const startDateTimeControl = this.getStartDateTimeFormControl(); if (newStartDateTime.getTime() >= endDateTimeTs) { - startDateTimeControl.patchValue(new Date(endDateTimeTs - MINUTE), {onlySelf: true, emitEvent: false}); + this.deleteTimeseriesFormGroup.get('startDateTime') + .patchValue(new Date(endDateTimeTs - MINUTE), {onlySelf: true, emitEvent: false}); } else { - startDateTimeControl.patchValue(newStartDateTime, {onlySelf: true, emitEvent: false}); + this.deleteTimeseriesFormGroup.get('startDateTime') + .patchValue(newStartDateTime, {onlySelf: true, emitEvent: false}); } } } @@ -137,11 +140,12 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { onEndDateTimeChange(newEndDateTime: Date) { if (newEndDateTime) { const startDateTimeTs = this.deleteTimeseriesFormGroup.get('startDateTime').value.getTime(); - const endDateTimeControl = this.getEndDateTimeFormControl(); if (newEndDateTime.getTime() <= startDateTimeTs) { - endDateTimeControl.patchValue(new Date(startDateTimeTs + MINUTE), {onlySelf: true, emitEvent: false}); + this.deleteTimeseriesFormGroup.get('endDateTime') + .patchValue(new Date(startDateTimeTs + MINUTE), {onlySelf: true, emitEvent: false}); } else { - endDateTimeControl.patchValue(newEndDateTime, {onlySelf: true, emitEvent: false}); + this.deleteTimeseriesFormGroup.get('endDateTime') + .patchValue(newEndDateTime, {onlySelf: true, emitEvent: false}); } } } From 41b0949046ce2792b9f24dbc37520d9d43218193 Mon Sep 17 00:00:00 2001 From: rusikv Date: Wed, 2 Aug 2023 18:17:59 +0300 Subject: [PATCH 130/166] Change attribute dialog UntypedFormGroup to FormGroup, simplified if statement on add --- .../attribute/add-attribute-dialog.component.ts | 11 +++++------ .../attribute/attribute-table.component.html | 2 +- .../components/attribute/attribute-table.component.ts | 1 + 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts index 55746b7347..aadb96c142 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts @@ -19,7 +19,7 @@ import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormGroupDirective, NgForm, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; import { EntityId } from '@shared/models/id/entity-id'; import { Router } from '@angular/router'; import { DialogComponent } from '@app/shared/components/dialog.component'; @@ -41,7 +41,7 @@ export interface AddAttributeDialogData { export class AddAttributeDialogComponent extends DialogComponent implements OnInit, ErrorStateMatcher { - attributeFormGroup: UntypedFormGroup; + attributeFormGroup: FormGroup; submitted = false; @@ -53,7 +53,7 @@ export class AddAttributeDialogComponent extends DialogComponent, - public fb: UntypedFormBuilder) { + public fb: FormBuilder) { super(store, router, dialogRef); } @@ -66,7 +66,7 @@ export class AddAttributeDialogComponent extends DialogComponent
-
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss index e86f828111..a82f9c2f8b 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss @@ -45,8 +45,6 @@ } .preview { - width: 100%; - height: 100%; max-width: 100%; max-height: 100%; object-fit: contain; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html index 788c997740..356612564f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html @@ -45,7 +45,7 @@ {{ 'widgets.value-card.label' | translate }}
- +
- + @@ -75,10 +75,10 @@
widgets.value-card.value
- - + + -
widget-config.decimals-suffix
+
widget-config.decimals-suffix
@@ -87,11 +87,11 @@
-
+
{{ 'widgets.value-card.date' | translate }} -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html index 62f34f3d28..63f936195a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html @@ -28,7 +28,7 @@ (removed)="removeKey()">
-
+
{{ 'datakey.configuration' | translate }}
+
+ datakey.data-generation-func +
+ + +
datakey.aggregation @@ -80,72 +92,62 @@ {{ 'datakey.aggregation-type-hint-common' | translate }}
-
- datakey.delta-calculation - - - - - {{ 'datakey.enable-delta-calculation' | translate }} - - {{ 'datakey.enable-delta-calculation-hint' | translate }} - - - -
- - widgets.chart.time-for-comparison - - - {{ 'widgets.chart.time-for-comparison-previous-interval' | translate }} - - - {{ 'widgets.chart.time-for-comparison-days' | translate }} - - - {{ 'widgets.chart.time-for-comparison-weeks' | translate }} - - - {{ 'widgets.chart.time-for-comparison-months' | translate }} - - - {{ 'widgets.chart.time-for-comparison-years' | translate }} - - - {{ 'widgets.chart.time-for-comparison-custom-interval' | translate }} - - - - - widgets.chart.custom-interval-value - - - - datakey.delta-calculation-result - - - {{ comparisonResultTypeTranslations.get(comparisonResultTypes[comparisonResultType]) | translate }} - - - -
-
-
-
-
- datakey.data-generation-func -
- - -
+
+
datakey.delta-calculation
+ + + + + {{ 'datakey.enable-delta-calculation' | translate }} + + {{ 'datakey.enable-delta-calculation-hint' | translate }} + + + +
+ + widgets.chart.time-for-comparison + + + {{ 'widgets.chart.time-for-comparison-previous-interval' | translate }} + + + {{ 'widgets.chart.time-for-comparison-days' | translate }} + + + {{ 'widgets.chart.time-for-comparison-weeks' | translate }} + + + {{ 'widgets.chart.time-for-comparison-months' | translate }} + + + {{ 'widgets.chart.time-for-comparison-years' | translate }} + + + {{ 'widgets.chart.time-for-comparison-custom-interval' | translate }} + + + + + widgets.chart.custom-interval-value + + + + datakey.delta-calculation-result + + + {{ comparisonResultTypeTranslations.get(comparisonResultTypes[comparisonResultType]) | translate }} + + + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html index e024af20ee..3226ffed5e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html @@ -47,7 +47,7 @@ drag_indicator
-
+
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss index 1664dafb7f..574d1df38e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss @@ -70,6 +70,9 @@ text-overflow: ellipsis; white-space: nowrap; } + &.tb-chip-icon { + min-width: 24px; + } .mat-icon.tb-datakey-icon { margin-right: 4px; margin-left: 4px; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/datasources.component.html b/ui-ngx/src/app/modules/home/components/widget/config/datasources.component.html index 2e4a984858..0ee6a7c508 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/datasources.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/datasources.component.html @@ -44,29 +44,27 @@ [cdkDropListDisabled]="dragDisabled" formArrayName="datasources"> -
-
+
+
{{$index + 1}}
-
-
- +
+
+ -
-
- - + + {{ 'widgets.table.use-cell-style-function' | translate }} @@ -86,8 +86,8 @@
- - + + {{ 'widgets.table.use-cell-content-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.html index 4ec8f31be6..e304def2c8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.html @@ -94,7 +94,7 @@ {{ 'widgets.table.display-pagination' | translate }} -
+
widgets.table.default-page-size
@@ -104,8 +104,8 @@
widgets.table.rows
- - + + {{ 'widgets.table.use-row-style-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-key-settings.component.html index 02d51ee325..faad357026 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-key-settings.component.html @@ -62,8 +62,8 @@
- - + + {{ 'widgets.table.use-cell-style-function' | translate }} @@ -86,8 +86,8 @@
- - + + {{ 'widgets.table.use-cell-content-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html index f8867b9f6a..c9c57d8843 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html @@ -41,7 +41,7 @@
widgets.table.columns
-
+
{{ 'widgets.table.display-entity-name' | translate }} @@ -49,7 +49,7 @@
-
+
{{ 'widgets.table.display-entity-label' | translate }} @@ -91,7 +91,7 @@ {{ 'widgets.table.display-pagination' | translate }} -
+
widgets.table.default-page-size
@@ -101,8 +101,8 @@
widgets.table.rows
- - + + {{ 'widgets.table.use-row-style-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-key-settings.component.html index 6c79eed622..165f4eb0f7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-key-settings.component.html @@ -50,8 +50,8 @@
- - + + {{ 'widgets.table.use-cell-style-function' | translate }} @@ -74,8 +74,8 @@
- - + + {{ 'widgets.table.use-cell-content-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-latest-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-latest-key-settings.component.html index 97cbe28ae9..fd34b66af6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-latest-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-latest-key-settings.component.html @@ -59,8 +59,8 @@
- - + + {{ 'widgets.table.use-cell-style-function' | translate }} @@ -83,8 +83,8 @@
- - + + {{ 'widgets.table.use-cell-content-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html index 170d460f92..d70ae9964b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html @@ -59,7 +59,7 @@ {{ 'widgets.table.display-pagination' | translate }} -
+
widgets.table.default-page-size
@@ -78,8 +78,8 @@ {{ 'widgets.table.hide-empty-lines' | translate }} - - + + {{ 'widgets.table.use-row-style-function' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html index 423c727a8d..71a9b60118 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html @@ -44,8 +44,8 @@ {{ 'widgets.value-card.icon' | translate }} -
- +
+ @@ -67,11 +67,11 @@
-
+
{{ 'widgets.value-card.date' | translate }} -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html index 96ff4d8559..a424b61606 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html @@ -32,8 +32,8 @@
- - + + {{ 'widgets.chart.show-line' | translate }} @@ -65,8 +65,8 @@
- - + + {{ 'widgets.chart.show-points' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html index 6f08b5f3f7..be2ae39255 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html @@ -17,7 +17,7 @@ -->
- +
{{ thresholdText() }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss index 150d516894..81f5d17bc6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss @@ -29,6 +29,9 @@ display: flex; flex-direction: row; align-items: stretch; + .mat-content { + overflow: hidden; + } .tb-threshold-header { flex: 1; display: flex; @@ -36,6 +39,7 @@ gap: 16px; align-items: center; padding-left: 16px; + overflow: hidden; .mat-divider-vertical { height: 100%; } @@ -47,6 +51,9 @@ font-weight: 400; line-height: 16px; letter-spacing: 0.15px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } .mat-expansion-indicator { margin-right: 22px; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html index 5dd7eaa2dc..4ea648c3b4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html @@ -77,8 +77,8 @@
widget-config.legend
- - + + {{ 'widget-config.legend' | translate }} @@ -119,8 +119,8 @@
widgets.chart.ticks
- - + + {{ 'widgets.chart.ticks' | translate }} @@ -171,8 +171,8 @@
widgets.chart.ticks
- - + + {{ 'widgets.chart.ticks' | translate }} @@ -234,8 +234,8 @@
widgets.chart.tooltip
- - + + {{ 'widgets.chart.tooltip' | translate }} @@ -274,8 +274,8 @@
widgets.chart.comparison-settings
- - + + {{ 'widgets.chart.enable-comparison' | translate }} @@ -350,8 +350,8 @@
widgets.chart.custom-legend-settings
- - + + {{ 'widgets.chart.enable-custom-legend' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html index ca5d4bc8b9..42c4472cca 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html @@ -20,7 +20,7 @@
widgets.background.background
- + {{ backgroundTypeTranslationsMap.get(type) | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts index 51d2ddec1b..465d3383e2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts @@ -94,7 +94,7 @@ export class BackgroundSettingsPanelComponent extends PageComponent implements O } applyColorSettings() { - const backgroundSettings = this.backgroundSettingsFormGroup.value; + const backgroundSettings = this.backgroundSettingsFormGroup.getRawValue(); this.backgroundSettingsApplied.emit(backgroundSettings); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.html index eb2bbd6711..52bd5907bd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.html @@ -17,7 +17,7 @@ -->
- +
{{ label }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html index 3cc771e94f..726aed776e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html @@ -17,7 +17,7 @@ -->
- + {{ 'widgets.value-source.predefined-value' | translate }} @@ -32,7 +32,7 @@
-
+
widgets.value-source.source-entity-alias
-
+
widgets.value-source.source-entity-attribute
-
-
-
-
{{ (disabled ? 'dashboard.empty-image' : 'dashboard.no-image') | translate }}
- -
-
- -
-
-
-
-
- cloud_upload - image-input.drop-image-or - - +
+
+
{{ 'dashboard.empty-image' | translate }}
+
-
-
-
-
+
+
+ cloud_upload +
+ image-input.drag-and-drop +
+ image-input.or + + +
+
+
+
+ +
dashboard.maximum-upload-file-size
diff --git a/ui-ngx/src/app/shared/components/image-input.component.scss b/ui-ngx/src/app/shared/components/image-input.component.scss index a776bc8cd7..b7d05bef8d 100644 --- a/ui-ngx/src/app/shared/components/image-input.component.scss +++ b/ui-ngx/src/app/shared/components/image-input.component.scss @@ -15,9 +15,8 @@ */ @import "../../../scss/constants"; -$containerHeight: 120px !default; -$previewContainerWidth: 168px !default; -$previewSize: 96px !default; +$containerHeight: 96px !default; +$previewSize: 78px !default; :host { @@ -31,30 +30,39 @@ $previewSize: 96px !default; } .tb-image-select-container { - position: relative; width: 100%; height: $containerHeight; + display: flex; + gap: 12px; + align-items: center; } .image-container { - position: relative; - float: left; - height: $containerHeight; - padding: 12px; - margin-right: 8px; - background: rgba(0, 0, 0, 0.03); + background: #F3F6FA; border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.05); + padding: 8px 12px 8px 8px; + display: flex; + align-items: center; + gap: 12px; + &.disabled { + padding: 8px; + } } - .image-content-container { - background: #FFFFFF; - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 4px; - padding-left: 8px; + .tb-image-preview-container { + width: $previewSize; height: $previewSize; - &.no-padding { - padding-left: 0px; - } + border: 1px solid rgba(0, 0, 0, 0.12); + background: #fff; + display: flex; + align-items: center; + justify-content: center; + } + + .tb-image-preview-text { + font-size: 14px; + text-align: center; } .tb-image-preview { @@ -64,57 +72,18 @@ $previewSize: 96px !default; max-height: $previewSize - 2px; } - .tb-image-preview-container { - position: relative; - float: left; - width: $previewSize; - height: $previewSize; - margin-top: -1px; - margin-bottom: -1px; - border: 1px solid rgba(0, 0, 0, 0.54); - - div { - width: 100%; - font-size: 18px; - text-align: center; - } - - div, - .tb-image-preview { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } - } - - .tb-image-clear-container { - position: relative; - float: right; - height: $previewSize; - display: flex; - align-items: center; - &.full-height { - height: $containerHeight; - } - } - .file-input { display: none; } .tb-flow-drop { - position: relative; - height: $containerHeight; + height: 100%; + flex: 1; overflow: hidden; border: 2px dashed rgba(0, 0, 0, 0.2); border-radius: 4px; box-sizing: border-box; - &.float-left { - float: left; - } - .upload-label { width: 100%; height: 100%; @@ -123,11 +92,29 @@ $previewSize: 96px !default; flex-direction: row; justify-content: center; align-items: center; - font-size: 16px; - color: rgba(0, 0, 0, 0.54); - text-align: center; + gap: 8px; .mat-icon { - margin-right: 17px; + color: rgba(0,0,0,0.12); + } + .upload-text-area { + display: flex; + flex-direction: column; + align-items: center; + font-size: 16px; + line-height: 24px; + color: rgba(0, 0, 0, 0.54); + text-align: center; + .hide-xs { + @media #{$mat-xs} { + display: none; + } + } + } + .upload-button-area { + display: flex; + justify-content: center; + align-items: flex-start; + gap: 6px; } } } @@ -138,13 +125,14 @@ $previewSize: 96px !default; } :host ::ng-deep { - button.browse-file { + button.mat-mdc-button.mat-mdc-button-base.browse-file { padding: 0; + min-width: 0; + height: 24px; font-size: 16px; label { display: block; cursor: pointer; - padding: 0 16px; } } } diff --git a/ui-ngx/src/app/shared/components/image-input.component.ts b/ui-ngx/src/app/shared/components/image-input.component.ts index 026b64a413..f5b80aa1e0 100644 --- a/ui-ngx/src/app/shared/components/image-input.component.ts +++ b/ui-ngx/src/app/shared/components/image-input.component.ts @@ -65,9 +65,6 @@ export class ImageInputComponent extends PageComponent implements AfterViewInit, @Input() disabled: boolean; - @Input() - showClearButton = true; - @Input() showPreview = true; diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.scss b/ui-ngx/src/app/shared/components/toggle-header.component.scss index 7a41032961..ac24f7bf29 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.scss +++ b/ui-ngx/src/app/shared/components/toggle-header.component.scss @@ -29,10 +29,12 @@ display: block; } } - .tb-toggle-container { + .tb-toggle-container, .tb-toggle-header-select { display: inline-grid; grid-column: 2; overflow: hidden; + } + .tb-toggle-container { &.tb-disable-pagination { overflow: visible; } diff --git a/ui-ngx/src/app/shared/components/toggle-header.component.ts b/ui-ngx/src/app/shared/components/toggle-header.component.ts index a7bddfc7b4..5781f9fac9 100644 --- a/ui-ngx/src/app/shared/components/toggle-header.component.ts +++ b/ui-ngx/src/app/shared/components/toggle-header.component.ts @@ -16,7 +16,7 @@ import { AfterContentChecked, - AfterContentInit, + AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectorRef, Component, @@ -117,7 +117,8 @@ export abstract class _ToggleBase extends PageComponent implements AfterContentI templateUrl: './toggle-header.component.html', styleUrls: ['./toggle-header.component.scss'] }) -export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterViewInit, AfterContentInit, AfterContentChecked, OnDestroy { +export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterViewInit, AfterContentInit, + AfterContentChecked, AfterViewChecked, OnDestroy { @ViewChild('toggleGroup', {static: false}) toggleGroup: ElementRef; @@ -130,6 +131,7 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV @HostBinding('class.tb-toggle-header-pagination-controls-enabled') private showPaginationControls = false; + private _showPaginationControlsChanged = false; private toggleGroupResize$: ResizeObserver; @@ -254,6 +256,14 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV } } + ngAfterViewChecked() { + if (this._showPaginationControlsChanged) { + this.scrollToToggleOptionValue(); + this._showPaginationControlsChanged = false; + this.cd.markForCheck(); + } + } + trackByHeaderOption(index: number, option: ToggleHeaderOption){ return option.value; } @@ -301,10 +311,13 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV private scrollToToggleOptionValue() { if (this.buttonToggleGroup && this.buttonToggleGroup.selected) { const selectedToggleButton = this.buttonToggleGroup.selected as MatButtonToggle; + const index = this.options.findIndex(o => o.value === selectedToggleButton.value); + const isLast = index === this.options.length - 1; + const isFirst = index === 0; const viewLength = this.toggleGroupContainer.nativeElement.offsetWidth; const {offsetLeft, offsetWidth} = (selectedToggleButton._buttonElement.nativeElement.offsetParent as HTMLElement); - const labelBeforePos = offsetLeft; // this.toggleGroup.nativeElement.offsetWidth - offsetLeft; - const labelAfterPos = labelBeforePos + offsetWidth; + const labelBeforePos = isFirst ? 0 : offsetLeft; + const labelAfterPos = isLast ? this.toggleGroup.nativeElement.scrollWidth : labelBeforePos + offsetWidth; const beforeVisiblePos = this.scrollDistance; const afterVisiblePos = this.scrollDistance + viewLength; if (labelBeforePos < beforeVisiblePos) { @@ -331,9 +344,7 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV if (!isEnabled) { this.scrollDistance = 0; } else { - setTimeout(() => { - this.scrollToToggleOptionValue(); - }, 0); + this._showPaginationControlsChanged = true; } this.cd.markForCheck(); this.showPaginationControls = isEnabled; @@ -373,11 +384,13 @@ export class ToggleHeaderComponent extends _ToggleBase implements OnInit, AfterV } private updateToggleHeaderScrollPosition() { - const scrollDistance = this.scrollDistance; - const translateX = -scrollDistance; - this.toggleGroup.nativeElement.style.transform = `translateX(${Math.round(translateX)}px)`; - if (this.platform.TRIDENT || this.platform.EDGE) { - this.toggleGroupContainer.nativeElement.scrollLeft = 0; + if (this.toggleGroupContainer) { + const scrollDistance = this.scrollDistance; + const translateX = -scrollDistance; + this.toggleGroup.nativeElement.style.transform = `translateX(${Math.round(translateX)}px)`; + if (this.platform.TRIDENT || this.platform.EDGE) { + this.toggleGroupContainer.nativeElement.scrollLeft = 0; + } } } } diff --git a/ui-ngx/src/app/shared/components/unit-input.component.html b/ui-ngx/src/app/shared/components/unit-input.component.html index 0ae14b8ba9..c15b232444 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.html +++ b/ui-ngx/src/app/shared/components/unit-input.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + Date: Thu, 3 Aug 2023 20:20:17 +0300 Subject: [PATCH 137/166] reverted refactoring with "return" use in the if statement & added @Override where required in DefaultTbContext & reverted changes to TbAbstractExternalNode --- .../actors/ruleChain/DefaultTbContext.java | 71 ++++++++++++++----- .../rule/engine/api/TbContext.java | 4 +- .../external/TbAbstractExternalNode.java | 12 +++- .../engine/profile/TbDeviceProfileNode.java | 48 ++++++------- 4 files changed, 89 insertions(+), 46 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index dc03a97254..f5a0cedf08 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -371,10 +371,12 @@ class DefaultTbContext implements TbContext { return TbMsg.transformMsgOriginator(origMsg, originator); } + @Override public TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId) { return entityActionMsg(customer, customer.getId(), ruleNodeId, ENTITY_CREATED); } + @Override public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) { DeviceProfile deviceProfile = null; if (device.getDeviceProfileId() != null) { @@ -383,6 +385,7 @@ class DefaultTbContext implements TbContext { return entityActionMsg(device, device.getId(), ruleNodeId, ENTITY_CREATED, deviceProfile); } + @Override public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) { AssetProfile assetProfile = null; if (asset.getAssetProfileId() != null) { @@ -391,18 +394,33 @@ class DefaultTbContext implements TbContext { return entityActionMsg(asset, asset.getId(), ruleNodeId, ENTITY_CREATED, assetProfile); } + @Override + public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) { + EntityId originator = alarm.getOriginator(); + HasRuleEngineProfile profile = getRuleEngineProfile(originator); + return entityActionMsg(alarm, originator, ruleNodeId, action, profile); + } + + @Override public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, TbMsgType actionMsgType) { + EntityId originator = alarm.getOriginator(); + HasRuleEngineProfile profile = getRuleEngineProfile(originator); + return entityActionMsg(alarm, originator, ruleNodeId, actionMsgType, profile); + } + + private HasRuleEngineProfile getRuleEngineProfile(EntityId originator) { HasRuleEngineProfile profile = null; - if (EntityType.DEVICE.equals(alarm.getOriginator().getEntityType())) { - DeviceId deviceId = new DeviceId(alarm.getOriginator().getId()); + if (EntityType.DEVICE.equals(originator.getEntityType())) { + DeviceId deviceId = new DeviceId(originator.getId()); profile = mainCtx.getDeviceProfileCache().get(getTenantId(), deviceId); - } else if (EntityType.ASSET.equals(alarm.getOriginator().getEntityType())) { - AssetId assetId = new AssetId(alarm.getOriginator().getId()); + } else if (EntityType.ASSET.equals(originator.getEntityType())) { + AssetId assetId = new AssetId(originator.getId()); profile = mainCtx.getAssetProfileCache().get(getTenantId(), assetId); } - return entityActionMsg(alarm, alarm.getOriginator(), ruleNodeId, actionMsgType, profile); + return profile; } + @Override public TbMsg attributesUpdatedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List attributes) { ObjectNode entityNode = JacksonUtil.newObjectNode(); if (attributes != null) { @@ -411,6 +429,7 @@ class DefaultTbContext implements TbContext { return attributesActionMsg(originator, ruleNodeId, scope, ATTRIBUTES_UPDATED, JacksonUtil.toString(entityNode)); } + @Override public TbMsg attributesDeletedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List keys) { ObjectNode entityNode = JacksonUtil.newObjectNode(); ArrayNode attrsArrayNode = entityNode.putArray("attributes"); @@ -423,14 +442,7 @@ class DefaultTbContext implements TbContext { private TbMsg attributesActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, TbMsgType actionMsgType, String msgData) { TbMsgMetaData tbMsgMetaData = getActionMetaData(ruleNodeId); tbMsgMetaData.putValue("scope", scope); - HasRuleEngineProfile profile = null; - if (EntityType.DEVICE.equals(originator.getEntityType())) { - DeviceId deviceId = new DeviceId(originator.getId()); - profile = mainCtx.getDeviceProfileCache().get(getTenantId(), deviceId); - } else if (EntityType.ASSET.equals(originator.getEntityType())) { - AssetId assetId = new AssetId(originator.getId()); - profile = mainCtx.getAssetProfileCache().get(getTenantId(), assetId); - } + HasRuleEngineProfile profile = getRuleEngineProfile(originator); return entityActionMsg(originator, tbMsgMetaData, msgData, actionMsgType, profile); } @@ -443,6 +455,26 @@ class DefaultTbContext implements TbContext { return entityActionMsg(entity, id, ruleNodeId, actionMsgType, null); } + @Deprecated(since = "3.5.2", forRemoval = true) + public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action, K profile) { + try { + return entityActionMsg(id, getActionMetaData(ruleNodeId), JacksonUtil.toString(JacksonUtil.valueToTree(entity)), action, profile); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e); + } + } + + @Deprecated(since = "3.5.2", forRemoval = true) + private TbMsg entityActionMsg(I id, TbMsgMetaData msgMetaData, String msgData, String action, K profile) { + String defaultQueueName = null; + RuleChainId defaultRuleChainId = null; + if (profile != null) { + defaultQueueName = profile.getDefaultQueueName(); + defaultRuleChainId = profile.getDefaultRuleChainId(); + } + return TbMsg.newMsg(defaultQueueName, action, id, msgMetaData, msgData, defaultRuleChainId, null); + } + public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, TbMsgType actionMsgType, K profile) { try { return entityActionMsg(id, getActionMetaData(ruleNodeId), JacksonUtil.toString(JacksonUtil.valueToTree(entity)), actionMsgType, profile); @@ -878,10 +910,17 @@ class DefaultTbContext implements TbContext { } private static String getFailureMessage(Throwable th) { - if (th == null) { - return null; + String failureMessage; + if (th != null) { + if (!StringUtils.isEmpty(th.getMessage())) { + failureMessage = th.getMessage(); + } else { + failureMessage = th.getClass().getSimpleName(); + } + } else { + failureMessage = null; } - return StringUtils.isNotEmpty(th.getMessage()) ? th.getMessage() : th.getClass().getSimpleName(); + return failureMessage; } private class SimpleTbQueueCallback implements TbQueueCallback { diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index be35e74321..fa47faf8aa 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -225,7 +225,9 @@ public interface TbContext { TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId); - // TODO: Does this changes the message? + @Deprecated(since = "3.5.2", forRemoval = true) + TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action); + TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, TbMsgType actionMsgType); TbMsg attributesUpdatedActionMsg(EntityId originator, RuleNodeId ruleNodeId, String scope, List attributes); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java index d0c3e28ec1..1fb000d709 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/external/TbAbstractExternalNode.java @@ -38,9 +38,17 @@ public abstract class TbAbstractExternalNode implements TbNode { protected void tellFailure(TbContext ctx, TbMsg tbMsg, Throwable t) { if (forceAck) { - ctx.enqueueForTellFailure(tbMsg.copyWithNewCtx(), t); + if (t == null) { + ctx.enqueueForTellNext(tbMsg.copyWithNewCtx(), TbNodeConnectionType.FAILURE); + } else { + ctx.enqueueForTellFailure(tbMsg.copyWithNewCtx(), t); + } } else { - ctx.tellFailure(tbMsg, t); + if (t == null) { + ctx.tellNext(tbMsg, TbNodeConnectionType.FAILURE); + } else { + ctx.tellFailure(tbMsg, t); + } } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index 058a70fdea..0dccabff80 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -111,13 +111,9 @@ public class TbDeviceProfileNode implements TbNode { if (msg.checkType(TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG)) { scheduleAlarmHarvesting(ctx, msg); harvestAlarms(ctx, System.currentTimeMillis()); - return; - } - if (msg.checkType(TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG)) { + } else if (msg.checkType(TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG)) { updateProfile(ctx, new DeviceProfileId(UUID.fromString(msg.getData()))); - return; - } - if (msg.checkType(TbMsgType.DEVICE_UPDATE_SELF_MSG)) { + } else if (msg.checkType(TbMsgType.DEVICE_UPDATE_SELF_MSG)) { JsonNode data = JacksonUtil.toJsonNode(msg.getData()); DeviceId deviceId = new DeviceId(UUID.fromString(data.get("deviceId").asText())); if (data.has("profileId")) { @@ -125,30 +121,28 @@ public class TbDeviceProfileNode implements TbNode { } else { removeDeviceState(deviceId); } - return; - } - if (EntityType.DEVICE.equals(originatorType)) { - DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); - if (msg.checkType(TbMsgType.ENTITY_UPDATED)) { - invalidateDeviceProfileCache(deviceId, msg.getData()); - ctx.tellSuccess(msg); - return; - } - if (msg.checkType(TbMsgType.ENTITY_DELETED)) { - removeDeviceState(deviceId); - ctx.tellSuccess(msg); - return; - } - DeviceState deviceState = getOrCreateDeviceState(ctx, deviceId, null, false); - if (deviceState == null) { - log.info("Device was not found! Most probably device [" + deviceId + "] has been removed from the database. Acknowledging msg."); - ctx.ack(msg); + } else { + if (EntityType.DEVICE.equals(originatorType)) { + DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); + if (msg.checkType(TbMsgType.ENTITY_UPDATED)) { + invalidateDeviceProfileCache(deviceId, msg.getData()); + ctx.tellSuccess(msg); + } else if (msg.checkType(TbMsgType.ENTITY_DELETED)) { + removeDeviceState(deviceId); + ctx.tellSuccess(msg); + } else { + DeviceState deviceState = getOrCreateDeviceState(ctx, deviceId, null, false); + if (deviceState != null) { + deviceState.process(ctx, msg); + } else { + log.info("Device was not found! Most probably device [" + deviceId + "] has been removed from the database. Acknowledging msg."); + ctx.ack(msg); + } + } } else { - deviceState.process(ctx, msg); + ctx.tellSuccess(msg); } - return; } - ctx.tellSuccess(msg); } @Override From ad847ff40c9f07916190f9eebbee95206b18fc01 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 3 Aug 2023 20:22:58 +0300 Subject: [PATCH 138/166] changed checkType and checkTypeOneOf to isTypeOf and isTypeOneOf --- .../thingsboard/server/common/msg/TbMsg.java | 6 +++--- .../TbCopyAttributesToEntityViewNode.java | 6 +++--- .../rule/engine/action/TbMsgCountNode.java | 2 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 2 +- .../deduplication/TbMsgDeduplicationNode.java | 2 +- .../rule/engine/delay/TbMsgDelayNode.java | 2 +- .../engine/edge/AbstractTbMsgPushNode.java | 14 +++++++------- .../rule/engine/mail/TbSendEmailNode.java | 2 +- .../engine/metadata/CalculateDeltaNode.java | 2 +- .../rule/engine/profile/DeviceState.java | 18 +++++++++--------- .../engine/profile/TbDeviceProfileNode.java | 10 +++++----- .../rule/engine/rpc/TbSendRPCRequestNode.java | 2 +- .../engine/telemetry/TbMsgAttributesNode.java | 2 +- .../engine/telemetry/TbMsgTimeseriesNode.java | 2 +- 14 files changed, 36 insertions(+), 36 deletions(-) diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index bec094b804..a987a4a253 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -468,13 +468,13 @@ public final class TbMsg implements Serializable { return ts; } - public boolean checkType(TbMsgType tbMsgType) { + public boolean isTypeOf(TbMsgType tbMsgType) { return tbMsgType != null && tbMsgType.name().equals(this.type); } - public boolean checkTypeOneOf(TbMsgType... types) { + public boolean isTypeOneOf(TbMsgType... types) { for (TbMsgType type : types) { - if (checkType(type)) { + if (isTypeOf(type)) { return true; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 8cd833ce66..c103dd4006 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -75,11 +75,11 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.checkTypeOneOf(ATTRIBUTES_UPDATED, ATTRIBUTES_DELETED, + if (msg.isTypeOneOf(ATTRIBUTES_UPDATED, ATTRIBUTES_DELETED, ACTIVITY_EVENT, INACTIVITY_EVENT, POST_ATTRIBUTES_REQUEST)) { if (!msg.getMetaData().getData().isEmpty()) { long now = System.currentTimeMillis(); - String scope = msg.checkType(POST_ATTRIBUTES_REQUEST) ? + String scope = msg.isTypeOf(POST_ATTRIBUTES_REQUEST) ? DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue(DataConstants.SCOPE); ListenableFuture> entityViewsFuture = @@ -91,7 +91,7 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { long startTime = entityView.getStartTimeMs(); long endTime = entityView.getEndTimeMs(); if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) { - if (msg.checkType(ATTRIBUTES_DELETED)) { + if (msg.isTypeOf(ATTRIBUTES_DELETED)) { List attributes = new ArrayList<>(); for (JsonElement element : JsonParser.parseString(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray()) { if (element.isJsonPrimitive()) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java index e43caf6bb8..021123a308 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java @@ -65,7 +65,7 @@ public class TbMsgCountNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.checkType(TbMsgType.MSG_COUNT_SELF_MSG) && msg.getId().equals(nextTickId)) { + if (msg.isTypeOf(TbMsgType.MSG_COUNT_SELF_MSG) && msg.getId().equals(nextTickId)) { JsonObject telemetryJson = new JsonObject(); telemetryJson.addProperty(this.telemetryPrefix + "_" + ctx.getServiceId(), messagesProcessed.longValue()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index cd32a44ea0..b31c98bc0f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -107,7 +107,7 @@ public class TbMsgGeneratorNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { log.trace("onMsg, config {}, msg {}", config, msg); - if (initialized.get() && msg.checkType(TbMsgType.GENERATOR_NODE_SELF_MSG) && msg.getId().equals(nextTickId)) { + if (initialized.get() && msg.isTypeOf(TbMsgType.GENERATOR_NODE_SELF_MSG) && msg.getId().equals(nextTickId)) { TbStopWatch sw = TbStopWatch.create(); withCallback(generate(ctx, msg), m -> { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index 1c0803770b..81bb3d6772 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -80,7 +80,7 @@ public class TbMsgDeduplicationNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - if (msg.checkType(TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG)) { + if (msg.isTypeOf(TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG)) { processDeduplication(ctx, msg.getOriginator()); } else { processOnRegularMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index d17415c1a6..5414cc2bbe 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -61,7 +61,7 @@ public class TbMsgDelayNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.checkType(TbMsgType.DELAY_TIMEOUT_SELF_MSG)) { + if (msg.isTypeOf(TbMsgType.DELAY_TIMEOUT_SELF_MSG)) { TbMsg pendingMsg = pendingMsgs.remove(UUID.fromString(msg.getData())); if (pendingMsg != null) { ctx.enqueueForTellNext( diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java index 3472ea011b..7888922dc6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java @@ -82,7 +82,7 @@ public abstract class AbstractTbMsgPushNode Date: Fri, 4 Aug 2023 13:03:02 +0300 Subject: [PATCH 139/166] UI: Aligned phone input flags container by input field --- ui-ngx/src/app/shared/components/phone-input.component.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/shared/components/phone-input.component.scss b/ui-ngx/src/app/shared/components/phone-input.component.scss index eb8d27646f..1ba5eabf3a 100644 --- a/ui-ngx/src/app/shared/components/phone-input.component.scss +++ b/ui-ngx/src/app/shared/components/phone-input.component.scss @@ -24,7 +24,6 @@ .phone-input-container { display: flex; - align-items: center; .phone-input { width: 100%; @@ -32,10 +31,11 @@ } .flags-select-container { - display: inline-block; + display: flex; + align-items: center; position: relative; width: 50px; - height: 100%; + height: 56px; margin-right: 5px; } From a85f9fbabcf68da5319598174825c0d4d38fee97 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 4 Aug 2023 17:49:52 +0300 Subject: [PATCH 140/166] UI: Refactoring delete telemetry --- ui-ngx/src/app/core/http/attribute.service.ts | 12 +++- .../attribute/attribute-table.component.html | 2 +- .../attribute/attribute-table.component.ts | 65 +++++++++-------- .../delete-timeseries-panel.component.html | 72 +++++++++---------- .../delete-timeseries-panel.component.scss | 28 ++++++-- .../delete-timeseries-panel.component.ts | 25 ++++--- 6 files changed, 114 insertions(+), 90 deletions(-) diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index c810a0af22..9022a47eef 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -53,8 +53,16 @@ export class AttributeService { startTs?: number, endTs?: number, rewriteLatestIfDeleted = false, deleteLatest = true, config?: RequestConfig): Observable { const keys = timeseries.map(attribute => encodeURIComponent(attribute.key)).join(','); - let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` + - `?keys=${keys}&deleteAllDataForKeys=${deleteAllDataForKeys}&rewriteLatestIfDeleted=${rewriteLatestIfDeleted}&deleteLatest=${deleteLatest}`; + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete?keys=${keys}`; + if (isDefinedAndNotNull(deleteAllDataForKeys)) { + url += `&deleteAllDataForKeys=${deleteAllDataForKeys}`; + } + if (isDefinedAndNotNull(rewriteLatestIfDeleted)) { + url += `&rewriteLatestIfDeleted=${rewriteLatestIfDeleted}`; + } + if (isDefinedAndNotNull(deleteLatest)) { + url += `&deleteLatest=${deleteLatest}`; + } if (isDefinedAndNotNull(startTs)) { url += `&startTs=${startTs}`; } diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index 10a5b03dba..d94f08e29f 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -198,7 +198,7 @@ edit - diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts index add0a5c1e0..0da11767a2 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts @@ -38,7 +38,7 @@ import { TranslateService } from '@ngx-translate/core'; import { MatDialog } from '@angular/material/dialog'; import { DialogService } from '@core/services/dialog.service'; import { Direction, SortOrder } from '@shared/models/page/sort-order'; -import { fromEvent, merge, Observable } from 'rxjs'; +import { fromEvent, merge } from 'rxjs'; import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; import { EntityId } from '@shared/models/id/entity-id'; import { @@ -48,7 +48,8 @@ import { isClientSideTelemetryType, LatestTelemetry, TelemetryType, - telemetryTypeTranslations, TimeseriesDeleteStrategy, + telemetryTypeTranslations, + TimeseriesDeleteStrategy, toTelemetryType } from '@shared/models/telemetry/telemetry.models'; import { AttributeDatasource } from '@home/models/datasource/attribute-datasource'; @@ -88,7 +89,8 @@ import { hidePageSizePixelValue } from '@shared/models/constants'; import { ResizeObserver } from '@juggle/resize-observer'; import { DELETE_TIMESERIES_PANEL_DATA, - DeleteTimeseriesPanelComponent, DeleteTimeseriesPanelData + DeleteTimeseriesPanelComponent, + DeleteTimeseriesPanelData } from '@home/components/attribute/delete-timeseries-panel.component'; @@ -383,15 +385,19 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI }); } - deleteTimeseries($event: Event, attribute?: AttributeData) { + deleteTimeseries($event: Event, telemetry?: AttributeData) { if ($event) { $event.stopPropagation(); } - const isMultipleDeletion = isUndefinedOrNull(attribute) && this.dataSource.selection.selected.length > 1; + const isMultipleDeletion = isUndefinedOrNull(telemetry) && this.dataSource.selection.selected.length > 1; const target = $event.target || $event.srcElement || $event.currentTarget; - const config = new OverlayConfig(); - config.backdropClass = 'cdk-overlay-transparent-backdrop'; - config.hasBackdrop = true; + const config = new OverlayConfig({ + panelClass: 'tb-filter-panel', + backdropClass: 'cdk-overlay-transparent-backdrop', + hasBackdrop: true, + maxWidth: 488, + width: '100%' + }); const connectedPosition: ConnectedPosition = { originX: 'start', originY: 'top', @@ -400,8 +406,6 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI }; config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) .withPositions([connectedPosition]); - config.maxWidth = '488px'; - config.width = '100%'; const overlayRef = this.overlay.create(config); overlayRef.backdropClick().subscribe(() => { overlayRef.dispose(); @@ -411,7 +415,7 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI { provide: DELETE_TIMESERIES_PANEL_DATA, useValue: { - isMultipleDeletion: isMultipleDeletion + isMultipleDeletion } as DeleteTimeseriesPanelData }, { @@ -425,31 +429,34 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI componentRef.onDestroy(() => { if (componentRef.instance.result !== null) { const result = componentRef.instance.result; - const deleteTimeseries = attribute ? [attribute]: this.dataSource.selection.selected; + const deleteTimeseries = telemetry ? [telemetry]: this.dataSource.selection.selected; let deleteAllDataForKeys = false; let rewriteLatestIfDeleted = false; let startTs = null; let endTs = null; let deleteLatest = true; - if (result.strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA) { - deleteAllDataForKeys = true; - } - if (result.strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_EXCEPT_LATEST_VALUE) { - deleteAllDataForKeys = true; - deleteLatest = false; - } - if (result.strategy === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE) { - rewriteLatestIfDeleted = result.rewriteLatest; - startTs = deleteTimeseries[0].lastUpdateTs; - endTs = startTs + 1; - } - if (result.strategy === TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD) { - startTs = result.startDateTime.getTime(); - endTs = result.endDateTime.getTime(); - rewriteLatestIfDeleted = result.rewriteLatest; + switch (result.strategy) { + case TimeseriesDeleteStrategy.DELETE_ALL_DATA: + deleteAllDataForKeys = true; + break; + case TimeseriesDeleteStrategy.DELETE_ALL_DATA_EXCEPT_LATEST_VALUE: + deleteAllDataForKeys = true; + deleteLatest = false; + break; + case TimeseriesDeleteStrategy.DELETE_LATEST_VALUE: + rewriteLatestIfDeleted = result.rewriteLatest; + startTs = deleteTimeseries[0].lastUpdateTs; + endTs = startTs + 1; + break; + case TimeseriesDeleteStrategy.DELETE_ALL_DATA_FOR_TIME_PERIOD: + startTs = result.startDateTime.getTime(); + endTs = result.endDateTime.getTime(); + rewriteLatestIfDeleted = result.rewriteLatest; + break; } this.attributeService.deleteEntityTimeseries(this.entityIdValue, deleteTimeseries, deleteAllDataForKeys, - startTs, endTs, rewriteLatestIfDeleted, deleteLatest).subscribe(() => this.reloadAttributes()); + startTs, endTs, rewriteLatestIfDeleted, deleteLatest) + .subscribe(() => this.reloadAttributes()); } }); } diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html index aabac0bab4..5778fc6805 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html @@ -16,47 +16,41 @@ --> - - -

{{ "attribute.delete-timeseries.delete-strategy" | translate }}

- - -
-
- - attribute.delete-timeseries.strategy - - - {{ strategiesTranslationsMap.get(strategy) | translate }} - - + +

{{ "attribute.delete-timeseries.delete-strategy" | translate }}

+ + +
+ + + attribute.delete-timeseries.strategy + + + {{ strategiesTranslationsMap.get(strategy) | translate }} + + + +
+ + attribute.delete-timeseries.start-time + + + + + + attribute.delete-timeseries.ends-on + + + -
-
- - attribute.delete-timeseries.start-time - - - - - - attribute.delete-timeseries.ends-on - - - - -
-
-
- - {{ "attribute.delete-timeseries.rewrite-latest-value" | translate }} - -
+ + {{ "attribute.delete-timeseries.rewrite-latest-value" | translate }} +
diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss index d223b29b47..c9a0e527f7 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss @@ -13,16 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@import '../../../../../scss/constants'; :host { width: 100%; - background-color: #fff; - box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3), 0px 2px 6px 2px rgba(0, 0, 0, 0.15); - border-radius: 4px; -} -:host ::ng-deep{ - form .mat-toolbar { + .mat-toolbar { background: none; } + + .tb-form-settings { + flex-direction: column; + gap: 16px; + padding-top: 0; + } + + .tb-select-interval { + display: flex; + flex-direction: row; + gap: 16px; + @media #{$mat-xs} { + flex-direction: column; + gap: 0; + } + } + + .tb-slide-toggle { + margin-bottom: 8px; + } } diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts index 8adb9bcd5a..94c66b3734 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.ts @@ -51,34 +51,33 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { strategiesTranslationsMap = timeseriesDeleteStrategyTranslations; - multipleDeletionStrategies = [ + private multipleDeletionStrategies = new Set([ TimeseriesDeleteStrategy.DELETE_ALL_DATA, TimeseriesDeleteStrategy.DELETE_ALL_DATA_EXCEPT_LATEST_VALUE - ]; + ]); private destroy$ = new Subject(); - constructor(@Inject(DELETE_TIMESERIES_PANEL_DATA) public data: DeleteTimeseriesPanelData, - public overlayRef: OverlayRef, - public fb: FormBuilder) { } + constructor(@Inject(DELETE_TIMESERIES_PANEL_DATA) private data: DeleteTimeseriesPanelData, + private overlayRef: OverlayRef, + private fb: FormBuilder) { } ngOnInit(): void { const today = new Date(); if (this.data.isMultipleDeletion) { - this.strategiesTranslationsMap = new Map(Array.from(this.strategiesTranslationsMap.entries()) - .filter(([strategy]) => { - return this.multipleDeletionStrategies.includes(strategy); - })) + this.strategiesTranslationsMap = new Map(Array.from(this.strategiesTranslationsMap) + .filter(([strategy]) => this.multipleDeletionStrategies.has(strategy))) } this.deleteTimeseriesFormGroup = this.fb.group({ strategy: [TimeseriesDeleteStrategy.DELETE_ALL_DATA], startDateTime: [ { value: new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()), disabled: true }, - [Validators.required] + Validators.required ], - endDateTime: [{ value: today, disabled: true }, [Validators.required]], + endDateTime: [{ value: today, disabled: true }, Validators.required], rewriteLatest: [true] }) + this.deleteTimeseriesFormGroup.get('strategy').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe(value => { @@ -124,7 +123,7 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { return this.deleteTimeseriesFormGroup.get('strategy').value === TimeseriesDeleteStrategy.DELETE_LATEST_VALUE; } - onStartDateTimeChange(newStartDateTime: Date) { + private onStartDateTimeChange(newStartDateTime: Date) { if (newStartDateTime) { const endDateTimeTs = this.deleteTimeseriesFormGroup.get('endDateTime').value.getTime(); if (newStartDateTime.getTime() >= endDateTimeTs) { @@ -137,7 +136,7 @@ export class DeleteTimeseriesPanelComponent implements OnInit, OnDestroy { } } - onEndDateTimeChange(newEndDateTime: Date) { + private onEndDateTimeChange(newEndDateTime: Date) { if (newEndDateTime) { const startDateTimeTs = this.deleteTimeseriesFormGroup.get('startDateTime').value.getTime(); if (newEndDateTime.getTime() <= startDateTimeTs) { From fdceb86b319d33b02dc545a213b40ccd8fd4606f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 4 Aug 2023 19:07:55 +0300 Subject: [PATCH 141/166] UI: Implement widget title font and color settings. --- .../src/app/core/services/dialog.service.ts | 5 +- .../alarms-table-basic-config.component.html | 22 ++++- .../alarms-table-basic-config.component.ts | 10 +++ .../basic/basic-widget-config.module.ts | 2 - ...entities-table-basic-config.component.html | 22 ++++- .../entities-table-basic-config.component.ts | 10 +++ .../simple-card-basic-config.component.ts | 2 +- ...meseries-table-basic-config.component.html | 22 ++++- ...timeseries-table-basic-config.component.ts | 10 +++ .../value-card-basic-config.component.ts | 2 +- .../chart/flot-basic-config.component.html | 22 ++++- .../chart/flot-basic-config.component.ts | 10 +++ .../config/widget-config-components.module.ts | 7 +- .../lib/cards/value-card-widget.component.ts | 2 +- .../lib/cards/value-card-widget.models.ts | 2 +- .../value-card-widget-settings.component.ts | 2 +- .../background-settings-panel.component.ts | 2 +- .../common/background-settings.component.ts | 2 +- .../common/color-settings-panel.component.ts | 2 +- .../common/color-settings.component.ts | 2 +- .../common/css-unit-select.component.html | 4 +- .../common/css-unit-select.component.ts | 7 +- .../common/date-format-select.component.ts | 2 +- .../date-format-settings-panel.component.ts | 2 +- .../common/font-settings-panel.component.html | 16 +++- .../common/font-settings-panel.component.ts | 43 +++++++--- .../common/font-settings.component.ts | 14 +++- .../common/widget-settings-common.module.ts | 84 +++++++++++++++++++ .../lib/settings/widget-settings.module.ts | 54 +----------- .../widget/widget-config.component.html | 20 ++++- .../widget/widget-config.component.ts | 8 ++ .../home/models/dashboard-component.models.ts | 13 +-- .../components/color-input.component.ts | 30 ++----- .../color-picker-panel.component.html | 8 ++ .../color-picker-panel.component.ts | 9 ++ .../dialog/color-picker-dialog.component.html | 1 + .../dialog/color-picker-dialog.component.ts | 3 + ui-ngx/src/app/shared/models/public-api.ts | 1 + .../models}/widget-settings.models.ts | 29 +++++-- ui-ngx/src/app/shared/models/widget.models.ts | 7 +- .../assets/locale/locale.constant-en_US.json | 3 +- ui-ngx/src/form.scss | 2 +- 42 files changed, 379 insertions(+), 141 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts rename ui-ngx/src/app/{modules/home/components/widget/config => shared/models}/widget-settings.models.ts (92%) diff --git a/ui-ngx/src/app/core/services/dialog.service.ts b/ui-ngx/src/app/core/services/dialog.service.ts index 2ca2d6d87e..6ba2687278 100644 --- a/ui-ngx/src/app/core/services/dialog.service.ts +++ b/ui-ngx/src/app/core/services/dialog.service.ts @@ -96,13 +96,14 @@ export class DialogService { return dialogRef.afterClosed(); } - colorPicker(color: string): Observable { + colorPicker(color: string, colorClearButton = false): Observable { return this.dialog.open(ColorPickerDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { - color + color, + colorClearButton }, autoFocus: false }).afterClosed(); diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html index ffddc9df59..f46686e0c0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html @@ -50,13 +50,24 @@
widget-config.card-appearance
-
+
{{ 'widget-config.card-title' | translate }} - - - +
+ + + + + + + +
@@ -68,6 +79,7 @@ formControlName="titleIcon">
@@ -84,12 +96,14 @@
{{ 'widget-config.text-color' | translate }}
{{ 'widget-config.background-color' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts index 33abc0670c..c67a59ac20 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts @@ -61,6 +61,8 @@ export class AlarmsTableBasicConfigComponent extends BasicWidgetConfigComponent columns: [this.getColumns(configData.config.alarmSource), []], showTitle: [configData.config.showTitle, []], title: [configData.config.settings?.alarmsTitle, []], + titleFont: [configData.config.titleFont, []], + titleColor: [configData.config.titleColor, []], showTitleIcon: [configData.config.showTitleIcon, []], titleIcon: [configData.config.titleIcon, []], iconColor: [configData.config.iconColor, []], @@ -82,6 +84,8 @@ export class AlarmsTableBasicConfigComponent extends BasicWidgetConfigComponent this.widgetConfig.config.showTitle = config.showTitle; this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; this.widgetConfig.config.settings.alarmsTitle = config.title; + this.widgetConfig.config.titleFont = config.titleFont; + this.widgetConfig.config.titleColor = config.titleColor; this.widgetConfig.config.showTitleIcon = config.showTitleIcon; this.widgetConfig.config.titleIcon = config.titleIcon; this.widgetConfig.config.iconColor = config.iconColor; @@ -100,6 +104,8 @@ export class AlarmsTableBasicConfigComponent extends BasicWidgetConfigComponent const showTitleIcon: boolean = this.alarmsTableWidgetConfigForm.get('showTitleIcon').value; if (showTitle) { this.alarmsTableWidgetConfigForm.get('title').enable(); + this.alarmsTableWidgetConfigForm.get('titleFont').enable(); + this.alarmsTableWidgetConfigForm.get('titleColor').enable(); this.alarmsTableWidgetConfigForm.get('showTitleIcon').enable({emitEvent: false}); if (showTitleIcon) { this.alarmsTableWidgetConfigForm.get('titleIcon').enable(); @@ -110,11 +116,15 @@ export class AlarmsTableBasicConfigComponent extends BasicWidgetConfigComponent } } else { this.alarmsTableWidgetConfigForm.get('title').disable(); + this.alarmsTableWidgetConfigForm.get('titleFont').disable(); + this.alarmsTableWidgetConfigForm.get('titleColor').disable(); this.alarmsTableWidgetConfigForm.get('showTitleIcon').disable({emitEvent: false}); this.alarmsTableWidgetConfigForm.get('titleIcon').disable(); this.alarmsTableWidgetConfigForm.get('iconColor').disable(); } this.alarmsTableWidgetConfigForm.get('title').updateValueAndValidity({emitEvent}); + this.alarmsTableWidgetConfigForm.get('titleFont').updateValueAndValidity({emitEvent}); + this.alarmsTableWidgetConfigForm.get('titleColor').updateValueAndValidity({emitEvent}); this.alarmsTableWidgetConfigForm.get('showTitleIcon').updateValueAndValidity({emitEvent: false}); this.alarmsTableWidgetConfigForm.get('titleIcon').updateValueAndValidity({emitEvent}); this.alarmsTableWidgetConfigForm.get('iconColor').updateValueAndValidity({emitEvent}); diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts index 3a3425975b..795d46984c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts @@ -34,7 +34,6 @@ import { TimeseriesTableBasicConfigComponent } from '@home/components/widget/config/basic/cards/timeseries-table-basic-config.component'; import { FlotBasicConfigComponent } from '@home/components/widget/config/basic/chart/flot-basic-config.component'; -import { WidgetSettingsModule } from '@home/components/widget/lib/settings/widget-settings.module'; import { AlarmsTableBasicConfigComponent } from '@home/components/widget/config/basic/alarm/alarms-table-basic-config.component'; @@ -57,7 +56,6 @@ import { imports: [ CommonModule, SharedModule, - WidgetSettingsModule, WidgetConfigComponentsModule ], exports: [ diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html index c68caab86e..b4c2dbd616 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html @@ -39,13 +39,24 @@
widget-config.card-appearance
-
+
{{ 'widget-config.card-title' | translate }} - - - +
+ + + + + + + +
@@ -57,6 +68,7 @@ formControlName="titleIcon">
@@ -72,12 +84,14 @@
{{ 'widget-config.text-color' | translate }}
{{ 'widget-config.background-color' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.ts index 2061832918..f407293064 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.ts @@ -80,6 +80,8 @@ export class EntitiesTableBasicConfigComponent extends BasicWidgetConfigComponen columns: [this.getColumns(configData.config.datasources), []], showTitle: [configData.config.showTitle, []], title: [configData.config.settings?.entitiesTitle, []], + titleFont: [configData.config.titleFont, []], + titleColor: [configData.config.titleColor, []], showTitleIcon: [configData.config.showTitleIcon, []], titleIcon: [configData.config.titleIcon, []], iconColor: [configData.config.iconColor, []], @@ -100,6 +102,8 @@ export class EntitiesTableBasicConfigComponent extends BasicWidgetConfigComponen this.widgetConfig.config.showTitle = config.showTitle; this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; this.widgetConfig.config.settings.entitiesTitle = config.title; + this.widgetConfig.config.titleFont = config.titleFont; + this.widgetConfig.config.titleColor = config.titleColor; this.widgetConfig.config.showTitleIcon = config.showTitleIcon; this.widgetConfig.config.titleIcon = config.titleIcon; this.widgetConfig.config.iconColor = config.iconColor; @@ -118,6 +122,8 @@ export class EntitiesTableBasicConfigComponent extends BasicWidgetConfigComponen const showTitleIcon: boolean = this.entitiesTableWidgetConfigForm.get('showTitleIcon').value; if (showTitle) { this.entitiesTableWidgetConfigForm.get('title').enable(); + this.entitiesTableWidgetConfigForm.get('titleFont').enable(); + this.entitiesTableWidgetConfigForm.get('titleColor').enable(); this.entitiesTableWidgetConfigForm.get('showTitleIcon').enable({emitEvent: false}); if (showTitleIcon) { this.entitiesTableWidgetConfigForm.get('titleIcon').enable(); @@ -128,11 +134,15 @@ export class EntitiesTableBasicConfigComponent extends BasicWidgetConfigComponen } } else { this.entitiesTableWidgetConfigForm.get('title').disable(); + this.entitiesTableWidgetConfigForm.get('titleFont').disable(); + this.entitiesTableWidgetConfigForm.get('titleColor').disable(); this.entitiesTableWidgetConfigForm.get('showTitleIcon').disable({emitEvent: false}); this.entitiesTableWidgetConfigForm.get('titleIcon').disable(); this.entitiesTableWidgetConfigForm.get('iconColor').disable(); } this.entitiesTableWidgetConfigForm.get('title').updateValueAndValidity({emitEvent}); + this.entitiesTableWidgetConfigForm.get('titleFont').updateValueAndValidity({emitEvent}); + this.entitiesTableWidgetConfigForm.get('titleColor').updateValueAndValidity({emitEvent}); this.entitiesTableWidgetConfigForm.get('showTitleIcon').updateValueAndValidity({emitEvent: false}); this.entitiesTableWidgetConfigForm.get('titleIcon').updateValueAndValidity({emitEvent}); this.entitiesTableWidgetConfigForm.get('iconColor').updateValueAndValidity({emitEvent}); diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts index 4238d6000c..283e39362a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts @@ -29,7 +29,7 @@ import { WidgetConfigComponent } from '@home/components/widget/widget-config.com import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component'; import { isUndefined } from '@core/utils'; -import { getLabel, setLabel } from '@home/components/widget/config/widget-settings.models'; +import { getLabel, setLabel } from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-simple-card-basic-config', diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html index 49196fac3a..5952c206b6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html @@ -39,13 +39,24 @@
widget-config.card-appearance
-
+
{{ 'widget-config.card-title' | translate }} - - - +
+ + + + + + + +
@@ -57,6 +68,7 @@ formControlName="titleIcon">
@@ -72,12 +84,14 @@
{{ 'widget-config.text-color' | translate }}
{{ 'widget-config.background-color' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts index ac1b12167c..a8c4206fa7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts @@ -66,6 +66,8 @@ export class TimeseriesTableBasicConfigComponent extends BasicWidgetConfigCompon columns: [this.getColumns(configData.config.datasources), []], showTitle: [configData.config.showTitle, []], title: [configData.config.title, []], + titleFont: [configData.config.titleFont, []], + titleColor: [configData.config.titleColor, []], showTitleIcon: [configData.config.showTitleIcon, []], titleIcon: [configData.config.titleIcon, []], iconColor: [configData.config.iconColor, []], @@ -85,6 +87,8 @@ export class TimeseriesTableBasicConfigComponent extends BasicWidgetConfigCompon this.widgetConfig.config.actions = config.actions; this.widgetConfig.config.showTitle = config.showTitle; this.widgetConfig.config.title = config.title; + this.widgetConfig.config.titleFont = config.titleFont; + this.widgetConfig.config.titleColor = config.titleColor; this.widgetConfig.config.showTitleIcon = config.showTitleIcon; this.widgetConfig.config.titleIcon = config.titleIcon; this.widgetConfig.config.iconColor = config.iconColor; @@ -104,6 +108,8 @@ export class TimeseriesTableBasicConfigComponent extends BasicWidgetConfigCompon const showTitleIcon: boolean = this.timeseriesTableWidgetConfigForm.get('showTitleIcon').value; if (showTitle) { this.timeseriesTableWidgetConfigForm.get('title').enable(); + this.timeseriesTableWidgetConfigForm.get('titleFont').enable(); + this.timeseriesTableWidgetConfigForm.get('titleColor').enable(); this.timeseriesTableWidgetConfigForm.get('showTitleIcon').enable({emitEvent: false}); if (showTitleIcon) { this.timeseriesTableWidgetConfigForm.get('titleIcon').enable(); @@ -114,11 +120,15 @@ export class TimeseriesTableBasicConfigComponent extends BasicWidgetConfigCompon } } else { this.timeseriesTableWidgetConfigForm.get('title').disable(); + this.timeseriesTableWidgetConfigForm.get('titleFont').disable(); + this.timeseriesTableWidgetConfigForm.get('titleColor').disable(); this.timeseriesTableWidgetConfigForm.get('showTitleIcon').disable({emitEvent: false}); this.timeseriesTableWidgetConfigForm.get('titleIcon').disable(); this.timeseriesTableWidgetConfigForm.get('iconColor').disable(); } this.timeseriesTableWidgetConfigForm.get('title').updateValueAndValidity({emitEvent}); + this.timeseriesTableWidgetConfigForm.get('titleFont').updateValueAndValidity({emitEvent}); + this.timeseriesTableWidgetConfigForm.get('titleColor').updateValueAndValidity({emitEvent}); this.timeseriesTableWidgetConfigForm.get('showTitleIcon').updateValueAndValidity({emitEvent: false}); this.timeseriesTableWidgetConfigForm.get('titleIcon').updateValueAndValidity({emitEvent}); this.timeseriesTableWidgetConfigForm.get('iconColor').updateValueAndValidity({emitEvent}); diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts index f00ec8e2e6..b535aaa7f8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts @@ -34,7 +34,7 @@ import { DateFormatSettings, getLabel, setLabel -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; import { valueCardDefaultSettings, ValueCardLayout, diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html index 952b3f9031..0d607c230d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html @@ -39,13 +39,24 @@
widget-config.card-appearance
-
+
{{ 'widget-config.card-title' | translate }} - - - +
+ + + + + + + +
@@ -57,6 +68,7 @@ formControlName="titleIcon">
@@ -70,12 +82,14 @@
{{ 'widget-config.text-color' | translate }}
{{ 'widget-config.background-color' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts index 9d3dc4f1dd..c77d0ecb76 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts @@ -66,6 +66,8 @@ export class FlotBasicConfigComponent extends BasicWidgetConfigComponent { series: [this.getSeries(configData.config.datasources), []], showTitle: [configData.config.showTitle, []], title: [configData.config.title, []], + titleFont: [configData.config.titleFont, []], + titleColor: [configData.config.titleColor, []], showTitleIcon: [configData.config.showTitleIcon, []], titleIcon: [configData.config.titleIcon, []], iconColor: [configData.config.iconColor, []], @@ -89,6 +91,8 @@ export class FlotBasicConfigComponent extends BasicWidgetConfigComponent { this.widgetConfig.config.actions = config.actions; this.widgetConfig.config.showTitle = config.showTitle; this.widgetConfig.config.title = config.title; + this.widgetConfig.config.titleFont = config.titleFont; + this.widgetConfig.config.titleColor = config.titleColor; this.widgetConfig.config.showTitleIcon = config.showTitleIcon; this.widgetConfig.config.titleIcon = config.titleIcon; this.widgetConfig.config.iconColor = config.iconColor; @@ -114,6 +118,8 @@ export class FlotBasicConfigComponent extends BasicWidgetConfigComponent { const showLegend: boolean = this.flotWidgetConfigForm.get('showLegend').value; if (showTitle) { this.flotWidgetConfigForm.get('title').enable(); + this.flotWidgetConfigForm.get('titleFont').enable(); + this.flotWidgetConfigForm.get('titleColor').enable(); this.flotWidgetConfigForm.get('showTitleIcon').enable({emitEvent: false}); if (showTitleIcon) { this.flotWidgetConfigForm.get('titleIcon').enable(); @@ -124,6 +130,8 @@ export class FlotBasicConfigComponent extends BasicWidgetConfigComponent { } } else { this.flotWidgetConfigForm.get('title').disable(); + this.flotWidgetConfigForm.get('titleFont').disable(); + this.flotWidgetConfigForm.get('titleColor').disable(); this.flotWidgetConfigForm.get('showTitleIcon').disable({emitEvent: false}); this.flotWidgetConfigForm.get('titleIcon').disable(); this.flotWidgetConfigForm.get('iconColor').disable(); @@ -134,6 +142,8 @@ export class FlotBasicConfigComponent extends BasicWidgetConfigComponent { this.flotWidgetConfigForm.get('legendConfig').disable(); } this.flotWidgetConfigForm.get('title').updateValueAndValidity({emitEvent}); + this.flotWidgetConfigForm.get('titleFont').updateValueAndValidity({emitEvent}); + this.flotWidgetConfigForm.get('titleColor').updateValueAndValidity({emitEvent}); this.flotWidgetConfigForm.get('showTitleIcon').updateValueAndValidity({emitEvent: false}); this.flotWidgetConfigForm.get('titleIcon').updateValueAndValidity({emitEvent}); this.flotWidgetConfigForm.get('iconColor').updateValueAndValidity({emitEvent}); diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts index 3777973eff..392a829955 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts @@ -29,6 +29,7 @@ import { FilterSelectComponent } from '@home/components/filter/filter-select.com import { WidgetSettingsModule } from '@home/components/widget/lib/settings/widget-settings.module'; import { WidgetSettingsComponent } from '@home/components/widget/config/widget-settings.component'; import { TimewindowConfigPanelComponent } from '@home/components/widget/config/timewindow-config-panel.component'; +import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings/common/widget-settings-common.module'; @NgModule({ declarations: @@ -48,7 +49,8 @@ import { TimewindowConfigPanelComponent } from '@home/components/widget/config/t imports: [ CommonModule, SharedModule, - WidgetSettingsModule + WidgetSettingsModule, + WidgetSettingsCommonModule ], exports: [ AlarmAssigneeSelectComponent, @@ -61,7 +63,8 @@ import { TimewindowConfigPanelComponent } from '@home/components/widget/config/t EntityAliasSelectComponent, FilterSelectComponent, TimewindowConfigPanelComponent, - WidgetSettingsComponent + WidgetSettingsComponent, + WidgetSettingsCommonModule ] }) export class WidgetConfigComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts index f495581571..ac2cb62fee 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts @@ -28,7 +28,7 @@ import { iconStyle, overlayStyle, textStyle -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; import { valueCardDefaultSettings, ValueCardLayout, ValueCardWidgetSettings } from './value-card-widget.models'; import { WidgetComponent } from '@home/components/widget/widget.component'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts index 23d9a30329..549ba4abcd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts @@ -21,7 +21,7 @@ import { constantColor, cssUnit, DateFormatSettings, Font, lastUpdateAgoDateFormat -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; export enum ValueCardLayout { square = 'square', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts index 6f76546ac1..ad50dde8d2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts @@ -30,7 +30,7 @@ import { DateFormatProcessor, DateFormatSettings, getLabel -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; @Component({ selector: 'tb-value-card-widget-settings', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts index 465d3383e2..ab94b95800 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts @@ -22,7 +22,7 @@ import { BackgroundSettings, BackgroundType, backgroundTypeTranslations, ComponentStyle -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { Store } from '@ngrx/store'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts index f8162575a3..62a2495942 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts @@ -22,7 +22,7 @@ import { BackgroundType, ComponentStyle, overlayStyle -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; import { MatButton } from '@angular/material/button'; import { TbPopoverService } from '@shared/components/popover.service'; import { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts index 2118bde6df..483674437d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts @@ -21,7 +21,7 @@ import { ColorSettings, ColorType, colorTypeTranslations -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { AbstractControl, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts index bffad41080..6d8ad1eefe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts @@ -16,7 +16,7 @@ import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ColorSettings, ColorType, ComponentStyle } from '@home/components/widget/config/widget-settings.models'; +import { ColorSettings, ColorType, ComponentStyle } from '@shared/models/widget-settings.models'; import { MatButton } from '@angular/material/button'; import { TbPopoverService } from '@shared/components/popover.service'; import { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.html index eaca1b6d40..1c00141b1d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.html @@ -16,7 +16,9 @@ --> - + + + {{ cssUnit }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts index dc593e9564..3d2658dae1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts @@ -16,7 +16,8 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms'; -import { cssUnit, cssUnits } from '@home/components/widget/config/widget-settings.models'; +import { cssUnit, cssUnits } from '@shared/models/widget-settings.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ selector: 'tb-css-unit-select', @@ -35,6 +36,10 @@ export class CssUnitSelectComponent implements OnInit, ControlValueAccessor { @Input() disabled: boolean; + @Input() + @coerceBoolean() + allowEmpty = false; + cssUnitsList = cssUnits; cssUnitFormControl: UntypedFormControl; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts index 413111ad1e..6393a92054 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts @@ -20,7 +20,7 @@ import { compareDateFormats, dateFormats, DateFormatSettings -} from '@home/components/widget/config/widget-settings.models'; +} from '@shared/models/widget-settings.models'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; import { MatButton } from '@angular/material/button'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.ts index 47f54fd5d5..be120b80f6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-settings-panel.component.ts @@ -16,7 +16,7 @@ import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; -import { DateFormatSettings } from '@home/components/widget/config/widget-settings.models'; +import { DateFormatSettings } from '@shared/models/widget-settings.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { UntypedFormControl, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html index 140c525ac2..36d488d766 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html @@ -51,7 +51,9 @@
widgets.widget-font.font-weight
- + + + {{ fontWeightTranslationsMap.has(weight) ? (fontWeightTranslationsMap.get(weight) | translate) : weight }} @@ -61,7 +63,9 @@
widgets.widget-font.font-style
- + + + {{ fontStyleTranslationsMap.get(style) | translate }} @@ -74,6 +78,14 @@
{{ previewText }}
+ +
diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts index 219ed0ec56..06d24f026b 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts @@ -23,6 +23,7 @@ import { DialogComponent } from '@shared/components/dialog.component'; export interface ColorPickerDialogData { color: string; + colorClearButton: boolean; } @Component({ @@ -33,6 +34,7 @@ export interface ColorPickerDialogData { export class ColorPickerDialogComponent extends DialogComponent { color: string; + colorClearButton: boolean; constructor(protected store: Store, protected router: Router, @@ -40,6 +42,7 @@ export class ColorPickerDialogComponent extends DialogComponent) { super(store, router, dialogRef); this.color = data.color; + this.colorClearButton = data.colorClearButton; } selectColor(color: string) { diff --git a/ui-ngx/src/app/shared/models/public-api.ts b/ui-ngx/src/app/shared/models/public-api.ts index aa93bece92..1b624259a9 100644 --- a/ui-ngx/src/app/shared/models/public-api.ts +++ b/ui-ngx/src/app/shared/models/public-api.ts @@ -54,6 +54,7 @@ export * from './settings.models'; export * from './tenant.model'; export * from './user.model'; export * from './user-settings.models'; +export * from './widget-settings.models'; export * from './widget.models'; export * from './widgets-bundle.model'; export * from './window-message.model'; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts b/ui-ngx/src/app/shared/models/widget-settings.models.ts similarity index 92% rename from ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts rename to ui-ngx/src/app/shared/models/widget-settings.models.ts index 0fa061de72..f7c1b6746e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/widget-settings.models.ts @@ -308,11 +308,30 @@ export const iconStyle = (size: number, sizeUnit: cssUnit): ComponentStyle => { }; }; -export const textStyle = (font: Font, lineHeight = '1.5', letterSpacing = '0.25px'): ComponentStyle => ({ - font: font.style + ' normal ' + font.weight + ' ' + (font.size+font.sizeUnit) + '/' + lineHeight + ' ' + font.family + - (font.family !== 'Roboto' ? ', Roboto' : ''), - letterSpacing -}); +export const textStyle = (font?: Font, lineHeight = '1.5', letterSpacing = '0.25px'): ComponentStyle => { + const style: ComponentStyle = { + lineHeight, + letterSpacing + }; + if (font?.style) { + style.fontStyle = font.style; + } + if (font?.weight) { + style.fontWeight = font.weight; + } + if (font?.size) { + style.fontSize = (font.size + (font.sizeUnit || 'px')); + } + if (font?.family) { + style.fontFamily = font.family + + (font.family !== 'Roboto' ? ', Roboto' : ''); + } + return style; +}; + +export const isFontSet = (font: Font): boolean => (!!font && !!font.style && !!font.weight && !!font.size && !!font.family); + +export const isFontPartiallySet = (font: Font): boolean => (!!font && (!!font.style || !!font.weight || !!font.size || !!font.family)); export const backgroundStyle = (background: BackgroundSettings): ComponentStyle => { if (background.type === BackgroundType.color) { diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index e0d9918540..c3b23048d1 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -40,6 +40,7 @@ import { Dashboard } from '@shared/models/dashboard.models'; import { IAliasController } from '@core/api/widget-api.models'; import { isEmptyStr } from '@core/utils'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { ComponentStyle, Font } from '@shared/models/widget-settings.models'; export enum widgetType { timeseries = 'timeseries', @@ -619,6 +620,8 @@ export enum WidgetConfigMode { export interface WidgetConfig { configMode?: WidgetConfigMode; title?: string; + titleFont?: Font; + titleColor?: string; titleIcon?: string; showTitle?: boolean; showTitleIcon?: boolean; @@ -639,9 +642,9 @@ export interface WidgetConfig { padding?: string; margin?: string; borderRadius?: string; - widgetStyle?: {[klass: string]: any}; + widgetStyle?: ComponentStyle; widgetCss?: string; - titleStyle?: {[klass: string]: any}; + titleStyle?: ComponentStyle; units?: string; decimals?: number; noDataDisplayMessage?: string; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 7f0a271a04..026ebf857e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -74,7 +74,8 @@ "reset": "Reset", "show-more": "Show more", "dont-show-again": "Do not show again", - "see-documentation": "See documentation" + "see-documentation": "See documentation", + "clear": "Clear" }, "aggregation": { "aggregation": "Aggregation", diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 14711b85b4..bef8bf621e 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -21,7 +21,7 @@ flex-direction: column; align-items: stretch; gap: 12px; - padding: 12px 12px 12px 16px; + padding: 12px 7px 12px 16px; .mat-mdc-form-field, tb-unit-input { width: auto; &.medium-width { From 6bc9148f772e724f4ec322d9790665fa3d0c5050 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 7 Aug 2023 11:20:53 +0300 Subject: [PATCH 142/166] added optional nosxss validation for attribute/telemetry value --- .../DefaultTelemetrySubscriptionService.java | 6 ++++- .../src/main/resources/thingsboard.yml | 2 ++ .../controller/TelemetryControllerTest.java | 17 +++++++++++++ .../server/dao/attributes/AttributeUtils.java | 8 +++--- .../dao/attributes/BaseAttributesService.java | 8 ++++-- .../attributes/CachedAttributesService.java | 6 +++-- .../thingsboard/server/dao/util/KvUtils.java | 25 ++++++++++++------- 7 files changed, 54 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 3f5e52796a..df3a15e765 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; @@ -78,6 +79,9 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer private ExecutorService tsCallBackExecutor; + @Value("${sql.ts.value_no_xss_validation:false}") + private boolean valueNoXssValidation; + public DefaultTelemetrySubscriptionService(AttributesService attrService, TimeseriesService tsService, @Lazy TbEntityViewService tbEntityViewService, @@ -135,7 +139,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer checkInternalEntity(entityId); boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null; if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { - KvUtils.validate(ts); + KvUtils.validate(ts, valueNoXssValidation); if (saveLatest) { saveAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback)); } else { diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index aa62a46d61..c8742921b0 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -269,11 +269,13 @@ sql: batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}" batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution + value_no_xss_validation: "${SQL_ATTRIBUTES_VALUE_NO_XSS_VALIDATION:false}" ts: batch_size: "${SQL_TS_BATCH_SIZE:10000}" batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}" batch_threads: "${SQL_TS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution + value_no_xss_validation: "${SQL_TS_VALUE_NO_XSS_VALIDATION:false}" ts_latest: batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}" batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}" diff --git a/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java index 47cac1b549..fc6fc33b8f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TelemetryControllerTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.controller; import org.junit.Test; +import org.springframework.test.context.TestPropertySource; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -25,6 +26,10 @@ import org.thingsboard.server.dao.service.DaoSqlTest; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @DaoSqlTest +@TestPropertySource(properties = { + "sql.attributes.value_no_xss_validation=true", + "sql.ts.value_no_xss_validation=true" +}) public class TelemetryControllerTest extends AbstractControllerTest { @Test @@ -39,6 +44,18 @@ public class TelemetryControllerTest extends AbstractControllerTest { doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", invalidRequestBody, String.class, status().isBadRequest()); } + @Test + public void testValueConstraintValidator() throws Exception { + loginTenantAdmin(); + Device device = createDevice(); + String correctRequestBody = "{\"data\": \"value\"}"; + doPostAsync("/api/plugins/telemetry/" + device.getId() + "/SHARED_SCOPE", correctRequestBody, String.class, status().isOk()); + doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", correctRequestBody, String.class, status().isOk()); + String invalidRequestBody = "{\"data\": \"alert(document)\\\">\"}"; + doPostAsync("/api/plugins/telemetry/" + device.getId() + "/SHARED_SCOPE", invalidRequestBody, String.class, status().isBadRequest()); + doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", invalidRequestBody, String.class, status().isBadRequest()); + } + private Device createDevice() throws Exception { String testToken = "TEST_TOKEN"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java index d1abeda5b6..192d56334d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeUtils.java @@ -30,12 +30,12 @@ public class AttributeUtils { Validator.validateString(scope, "Incorrect scope " + scope); } - public static void validate(List kvEntries) { - kvEntries.forEach(AttributeUtils::validate); + public static void validate(List kvEntries, boolean valueNoXssValidation) { + kvEntries.forEach(tsKvEntry -> validate(tsKvEntry, valueNoXssValidation)); } - public static void validate(AttributeKvEntry kvEntry) { - KvUtils.validate(kvEntry); + public static void validate(AttributeKvEntry kvEntry, boolean valueNoXssValidation) { + KvUtils.validate(kvEntry, valueNoXssValidation); if (kvEntry.getDataType() == null) { throw new IncorrectParameterException("Incorrect kvEntry. Data type can't be null"); } else { diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java index 09414ac750..f855c116e2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.attributes; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; @@ -45,6 +46,9 @@ import static org.thingsboard.server.dao.attributes.AttributeUtils.validate; public class BaseAttributesService implements AttributesService { private final AttributesDao attributesDao; + @Value("${sql.attributes.value_no_xss_validation:false}") + private boolean valueNoXssValidation; + public BaseAttributesService(AttributesDao attributesDao) { this.attributesDao = attributesDao; } @@ -82,14 +86,14 @@ public class BaseAttributesService implements AttributesService { @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) { validate(entityId, scope); - AttributeUtils.validate(attribute); + AttributeUtils.validate(attribute, valueNoXssValidation); return attributesDao.save(tenantId, entityId, scope, attribute); } @Override public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { validate(entityId, scope); - AttributeUtils.validate(attributes); + AttributeUtils.validate(attributes, valueNoXssValidation); List> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, scope, attribute)).collect(Collectors.toList()); return Futures.allAsList(saveFutures); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java index b95ce39d9a..faff81670b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java @@ -69,6 +69,8 @@ public class CachedAttributesService implements AttributesService { @Value("${cache.type:caffeine}") private String cacheType; + @Value("${sql.attributes.value_no_xss_validation:false}") + private boolean valueNoXssValidation; public CachedAttributesService(AttributesDao attributesDao, StatsFactory statsFactory, @@ -212,7 +214,7 @@ public class CachedAttributesService implements AttributesService { @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) { validate(entityId, scope); - AttributeUtils.validate(attribute); + AttributeUtils.validate(attribute, valueNoXssValidation); ListenableFuture future = attributesDao.save(tenantId, entityId, scope, attribute); return Futures.transform(future, key -> evict(entityId, scope, attribute, key), cacheExecutor); } @@ -220,7 +222,7 @@ public class CachedAttributesService implements AttributesService { @Override public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) { validate(entityId, scope); - AttributeUtils.validate(attributes); + AttributeUtils.validate(attributes, valueNoXssValidation); List> futures = new ArrayList<>(attributes.size()); for (var attribute : attributes) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java b/dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java index 788a19228b..e417a5a50a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/KvUtils.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.util; +import com.fasterxml.jackson.databind.JsonNode; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import org.thingsboard.server.common.data.kv.KvEntry; @@ -36,11 +37,11 @@ public class KvUtils { .maximumSize(100000).build(); } - public static void validate(List tsKvEntries) { - tsKvEntries.forEach(KvUtils::validate); + public static void validate(List tsKvEntries, boolean valueNoXssValidation) { + tsKvEntries.forEach(tsKvEntry -> validate(tsKvEntry, valueNoXssValidation)); } - public static void validate(KvEntry tsKvEntry) { + public static void validate(KvEntry tsKvEntry, boolean valueNoXssValidation) { if (tsKvEntry == null) { throw new IncorrectParameterException("Key value entry can't be null"); } @@ -55,14 +56,20 @@ public class KvUtils { throw new DataValidationException("Validation error: key length must be equal or less than 255"); } - if (validatedKeys.getIfPresent(key) != null) { - return; + if (validatedKeys.getIfPresent(key) == null) { + if (!NoXssValidator.isValid(key)) { + throw new DataValidationException("Validation error: key is malformed"); + } + validatedKeys.put(key, Boolean.TRUE); } - if (!NoXssValidator.isValid(key)) { - throw new DataValidationException("Validation error: key is malformed"); + if (valueNoXssValidation) { + Object value = tsKvEntry.getValue(); + if (value instanceof CharSequence || value instanceof JsonNode) { + if (!NoXssValidator.isValid(value.toString())) { + throw new DataValidationException("Validation error: value is malformed"); + } + } } - - validatedKeys.put(key, Boolean.TRUE); } } From 8faa1410b66cb2ef9d924b7a9e790881cb4c1d70 Mon Sep 17 00:00:00 2001 From: Andrii Landiak <50847617+AndriiLandiak@users.noreply.github.com> Date: Mon, 7 Aug 2023 13:21:02 +0300 Subject: [PATCH 143/166] Edge crud notification: implement event publisher strategy to process pubsub model for detecting changes in entities * Improve edge notification for entities' CRUD operations. Use service layer to notify instead of TbService * Improve queue service, delete unused class for edge event updates * Improve alarm delete and add handle fox delete dao event notification * Refactoring: provide notification for relations and alarms. Improve logic and bad edge event type using * Add entity type to SaveEvent to process correct message type to edge * Improve relation service publish event * Introduce EdgeEventSourcing service instead of saving edge events on controller/service layers. Part #2 * Improved stability of device edge test * Push credential updated event only in case update * Add tenantId to saveUser signature to send correct notification for listener * Fix tests to send correct notification msg to edge * Fix tests with correct action type * Add delete msg to edge for customer * Refactor ActionEntityEvent to use lombok builder * Remove unnecessary comments * Added edgeSynchronizationManager into BaseAlarmProcessor and BaseRelationProcessor * Fixed license header * Remove notification to edge from Version Control Service * Fixed alarm del processing - find related edges inside edge processor * Fix controller test for publish event to listener if entity was deleted * Added check for edge imitator messages during login as tenant admin * Refactoring: Added filtering of relation on EdgeEventSourcingListener * Refactored to be in sync with PE * Refactored edge test to be in sync with PE edge test changes * EdgeControllerTest - moved await block into separate method to reuse it * Fixed EdgeControllerTest * Fixed testAssignEdgeToCustomerFromDifferentTenant test * testSyncEdge - make stable * Refacroting - update utils method name to pop* in EdgeControllerTest * testSyncEdge - fixed order and nubmer of edge events * testGetEdgeEvents - check by pop items, and not by index to improve stability on slow machines * testGetEdgeEvents - added check that list is empty * Removed test debug output * EntityServiceTest - Fixed compilation error after merge * Improve service layer event publisher to process each notification and validate in listener * Improve BaseAlarmService to send notification to listener * Fix asset-device notification action to send delete to all edges * Delete unnecessary usage of sendMsgToEdge * Improve processEntityNotification to be in sync with changed needed for PE * Pull request review - minor refactoring * Fix tests after review-refactoring * Refactor tests to be in sync with PE * Fixed repeated update - added check for old_edge_event table existance before migration * DeviceEdgeProcessor - do edgeSynchronizationManager as soon as possible to avoid unnecessary downlinks * BaseEdgeProcessor - refactoring and remove duplicate methods. Introduce EdgeEventType.isAllEdgesRelated * Organize imports * Improve Edge test: add sync completed message to await * Minor refactoring for EdgeProcessor notification: asset and device * EdgeEventSourcingListener - updated logging to avoid null pointer exception * BaseAlarmService - added check for alarm to avoid NPE. EdgeEventSourcingListener - added try/catch blocks * EdgeEventSourcingListener - fixed error message log level --------- Co-authored-by: Volodymyr Babak --- .../main/data/upgrade/3.5.1/schema_update.sql | 29 +- .../server/actors/ActorSystemContext.java | 3 +- .../ruleChain/RuleEngineComponentActor.java | 2 +- .../config/RateLimitProcessingFilter.java | 4 +- .../server/controller/AuthController.java | 9 +- .../server/controller/BaseController.java | 22 -- .../server/controller/EdgeController.java | 22 +- .../controller/WidgetTypeController.java | 6 - .../controller/plugin/TbWebSocketHandler.java | 2 +- .../ThingsboardErrorResponseHandler.java | 1 - .../service/action/EntityActionService.java | 4 - .../edge/DefaultEdgeNotificationService.java | 66 ++-- .../edge/EdgeEventSourcingListener.java | 158 ++++++++++ .../edge/rpc/processor/BaseEdgeProcessor.java | 88 +++--- .../processor/alarm/AlarmEdgeProcessor.java | 49 +-- .../processor/alarm/BaseAlarmProcessor.java | 12 +- .../processor/asset/AssetEdgeProcessor.java | 7 - .../asset/AssetProfileEdgeProcessor.java | 8 - .../dashboard/DashboardEdgeProcessor.java | 7 - .../processor/device/BaseDeviceProcessor.java | 6 +- .../processor/device/DeviceEdgeProcessor.java | 9 +- .../device/DeviceProfileEdgeProcessor.java | 7 - .../entityview/EntityViewEdgeProcessor.java | 7 - .../ota/OtaPackageEdgeProcessor.java | 7 - .../processor/queue/QueueEdgeProcessor.java | 6 - .../relation/BaseRelationProcessor.java | 16 +- .../rule/RuleChainEdgeProcessor.java | 7 - .../rpc/processor/user/UserEdgeProcessor.java | 7 +- .../widget/WidgetBundleEdgeProcessor.java | 7 - .../widget/WidgetTypeEdgeProcessor.java | 6 - .../DefaultTbNotificationEntityService.java | 139 +-------- .../entitiy/TbNotificationEntityService.java | 50 +-- .../alarm/DefaultTbAlarmCommentService.java | 5 +- .../entitiy/alarm/DefaultTbAlarmService.java | 34 +- .../entitiy/asset/DefaultTbAssetService.java | 36 +-- .../profile/DefaultTbAssetProfileService.java | 11 +- .../customer/DefaultTbCustomerService.java | 15 +- .../dashboard/DefaultTbDashboardService.java | 49 ++- .../device/DefaultTbDeviceService.java | 32 +- .../DefaultTbDeviceProfileService.java | 11 +- .../entitiy/edge/DefaultTbEdgeService.java | 8 +- .../DefaultTbEntityRelationService.java | 21 +- .../DefaultTbEntityViewService.java | 67 ++-- .../ota/DefaultTbOtaPackageService.java | 19 +- .../entitiy/queue/DefaultTbQueueService.java | 5 - .../entitiy/user/DefaultUserService.java | 11 +- .../bundle/DefaultWidgetsBundleService.java | 11 +- .../DefaultSystemDataLoaderService.java | 2 +- .../service/mail/DefaultMailService.java | 1 - .../queue/DefaultTbClusterService.java | 8 - .../queue/DefaultTbCoreConsumerService.java | 4 +- .../rule/DefaultTbRuleChainService.java | 32 +- .../DefaultEntitiesExportImportService.java | 8 +- .../impl/AssetProfileImportService.java | 4 +- .../impl/BaseEntityImportService.java | 8 +- .../impl/DeviceProfileImportService.java | 4 +- .../impl/RuleChainImportService.java | 5 +- .../impl/WidgetsBundleImportService.java | 9 - .../DefaultEntitiesVersionControlService.java | 8 +- .../DefaultAlarmSubscriptionService.java | 4 +- .../server/utils/LwM2mObjectModelUtils.java | 2 - .../controller/AbstractNotifyEntityTest.java | 19 +- .../server/controller/AbstractWebTest.java | 18 +- .../controller/AlarmControllerTest.java | 29 +- .../controller/AssetControllerTest.java | 91 +++--- .../AssetProfileControllerTest.java | 4 +- .../controller/CustomerControllerTest.java | 12 +- .../controller/DashboardControllerTest.java | 37 ++- .../controller/DeviceControllerTest.java | 47 +-- .../DeviceProfileControllerTest.java | 4 +- .../server/controller/EdgeControllerTest.java | 294 ++++++++++++------ .../controller/EdgeEventControllerTest.java | 64 ++-- .../controller/EntityViewControllerTest.java | 32 +- .../controller/OtaPackageControllerTest.java | 2 +- .../controller/RuleChainControllerTest.java | 20 +- .../server/controller/UserControllerTest.java | 8 +- .../server/edge/AbstractEdgeTest.java | 109 +++---- .../server/edge/AssetEdgeTest.java | 5 +- .../server/edge/CustomerEdgeTest.java | 17 +- .../server/edge/DashboardEdgeTest.java | 4 +- .../server/edge/DeviceEdgeTest.java | 16 +- .../server/edge/EntityViewEdgeTest.java | 4 +- .../server/edge/RuleChainEdgeTest.java | 31 +- .../server/edge/TelemetryEdgeTest.java | 2 +- .../thingsboard/server/edge/UserEdgeTest.java | 60 ++-- .../server/edge/imitator/EdgeImitator.java | 13 +- .../provision/DeviceProvisionServiceTest.java | 2 - .../alarm/DefaultTbAlarmServiceTest.java | 17 +- .../DefaultTbAlarmCommentServiceTest.java | 5 +- .../server/cluster/TbClusterService.java | 2 - .../dao/edge/EdgeSynchronizationManager.java | 23 ++ .../server/dao/user/UserService.java | 2 +- .../common/data/edge/EdgeEventType.java | 47 +-- .../server/dao/alarm/BaseAlarmService.java | 45 ++- .../dao/asset/AssetProfileServiceImpl.java | 6 +- .../server/dao/asset/BaseAssetService.java | 11 + .../dao/customer/CustomerServiceImpl.java | 7 +- .../dao/dashboard/DashboardServiceImpl.java | 11 + .../device/DeviceCredentialsServiceImpl.java | 5 + .../dao/device/DeviceProfileServiceImpl.java | 5 + .../server/dao/device/DeviceServiceImpl.java | 12 +- .../DefaultEdgeSynchronizationManager.java | 34 ++ .../server/dao/edge/EdgeServiceImpl.java | 10 +- .../entity/AbstractCachedEntityService.java | 4 - .../dao/entity/AbstractEntityService.java | 7 +- .../dao/entityview/EntityViewServiceImpl.java | 13 +- .../dao/eventsourcing/ActionEntityEvent.java | 33 ++ .../dao/eventsourcing/DeleteEntityEvent.java | 31 ++ .../eventsourcing/RelationActionEvent.java | 28 ++ .../dao/eventsourcing/SaveEntityEvent.java | 30 ++ .../server/dao/ota/BaseOtaPackageService.java | 9 +- .../server/dao/queue/BaseQueueService.java | 8 +- .../dao/relation/BaseRelationService.java | 20 +- .../server/dao/rule/BaseRuleChainService.java | 18 +- .../server/dao/user/UserServiceImpl.java | 34 +- .../dao/widget/WidgetTypeServiceImpl.java | 13 +- .../dao/widget/WidgetsBundleServiceImpl.java | 12 +- .../dao/service/AlarmCommentServiceTest.java | 3 +- .../server/dao/service/AlarmServiceTest.java | 5 +- .../server/dao/service/EntityServiceTest.java | 2 +- .../server/dao/service/TenantServiceTest.java | 2 +- .../server/dao/service/UserServiceTest.java | 30 +- 122 files changed, 1524 insertions(+), 1171 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeSynchronizationManager.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/edge/DefaultEdgeSynchronizationManager.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/eventsourcing/ActionEntityEvent.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/eventsourcing/DeleteEntityEvent.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/eventsourcing/RelationActionEvent.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/eventsourcing/SaveEntityEvent.java diff --git a/application/src/main/data/upgrade/3.5.1/schema_update.sql b/application/src/main/data/upgrade/3.5.1/schema_update.sql index 281a7fcbfa..c8edf45cff 100644 --- a/application/src/main/data/upgrade/3.5.1/schema_update.sql +++ b/application/src/main/data/upgrade/3.5.1/schema_update.sql @@ -99,19 +99,22 @@ DECLARE p RECORD; partition_end_ts BIGINT; BEGIN - FOR p IN SELECT DISTINCT (created_time - created_time % partition_size_ms) AS partition_ts FROM old_edge_event - WHERE created_time >= start_time_ms AND created_time < end_time_ms - LOOP - partition_end_ts = p.partition_ts + partition_size_ms; - RAISE NOTICE '[edge_event] Partition to create : [%-%]', p.partition_ts, partition_end_ts; - EXECUTE format('CREATE TABLE IF NOT EXISTS edge_event_%s PARTITION OF edge_event ' || - 'FOR VALUES FROM ( %s ) TO ( %s )', p.partition_ts, p.partition_ts, partition_end_ts); - END LOOP; - - INSERT INTO edge_event (id, created_time, edge_id, edge_event_type, edge_event_uid, entity_id, edge_event_action, body, tenant_id, ts) - SELECT id, created_time, edge_id, edge_event_type, edge_event_uid, entity_id, edge_event_action, body, tenant_id, ts - FROM old_edge_event - WHERE created_time >= start_time_ms AND created_time < end_time_ms; + IF (SELECT exists(SELECT FROM pg_tables WHERE tablename = 'old_edge_event')) THEN + FOR p IN SELECT DISTINCT (created_time - created_time % partition_size_ms) AS partition_ts FROM old_edge_event + WHERE created_time >= start_time_ms AND created_time < end_time_ms + LOOP + partition_end_ts = p.partition_ts + partition_size_ms; + RAISE NOTICE '[edge_event] Partition to create : [%-%]', p.partition_ts, partition_end_ts; + EXECUTE format('CREATE TABLE IF NOT EXISTS edge_event_%s PARTITION OF edge_event ' || + 'FOR VALUES FROM ( %s ) TO ( %s )', p.partition_ts, p.partition_ts, partition_end_ts); + END LOOP; + INSERT INTO edge_event (id, created_time, edge_id, edge_event_type, edge_event_uid, entity_id, edge_event_action, body, tenant_id, ts) + SELECT id, created_time, edge_id, edge_event_type, edge_event_uid, entity_id, edge_event_action, body, tenant_id, ts + FROM old_edge_event + WHERE created_time >= start_time_ms AND created_time < end_time_ms; + ELSE + RAISE NOTICE 'Table old_edge_event does not exists, skipping migration'; + END IF; END; $$; -- EDGE EVENTS MIGRATION END diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index fc822ce226..f82db6ebe7 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -48,6 +48,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.tools.TbRateLimits; @@ -90,7 +91,6 @@ 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.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.component.ComponentDiscoveryService; @@ -115,7 +115,6 @@ import org.thingsboard.server.service.transport.TbCoreToTransportService; import javax.annotation.Nullable; import javax.annotation.PostConstruct; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.concurrent.ConcurrentHashMap; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java index 42a78b7965..dc7529200e 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java @@ -22,9 +22,9 @@ import org.thingsboard.server.actors.shared.ComponentMsgProcessor; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventTrigger; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbActorStopReason; -import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventTrigger; public abstract class RuleEngineComponentActor> extends ComponentActor { diff --git a/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java b/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java index 2ecc8590b8..66c1c9081d 100644 --- a/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java @@ -24,10 +24,10 @@ import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.exception.TenantProfileNotFoundException; -import org.thingsboard.server.common.msg.tools.TbRateLimitsException; -import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.common.data.limit.LimitedApi; +import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.service.security.model.SecurityUser; import javax.servlet.FilterChain; diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index 4512334d61..ac024093c6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -38,19 +38,18 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; import org.thingsboard.server.common.data.security.event.UserSessionInvalidationEvent; import org.thingsboard.server.common.data.security.model.JwtPair; import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; import org.thingsboard.server.service.security.model.ActivateUserRequest; import org.thingsboard.server.service.security.model.ChangePasswordRequest; @@ -123,8 +122,6 @@ public class AuthController extends BaseController { userCredentials.setPassword(passwordEncoder.encode(newPassword)); userService.replaceUserCredentials(securityUser.getTenantId(), userCredentials); - sendEntityNotificationMsg(getTenantId(), userCredentials.getUserId(), EdgeEventActionType.CREDENTIALS_UPDATED); - eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); ObjectNode response = JacksonUtil.newObjectNode(); response.put("token", tokenFactory.createAccessJwtToken(securityUser).getToken()); @@ -259,8 +256,6 @@ public class AuthController extends BaseController { } } - sendEntityNotificationMsg(user.getTenantId(), user.getId(), EdgeEventActionType.CREDENTIALS_UPDATED); - return tokenFactory.createTokenPair(securityUser); } diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 68a987a0bc..0af55d7c00 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -60,8 +60,6 @@ import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; -import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeInfo; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -142,8 +140,6 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.action.EntityActionService; import org.thingsboard.server.service.component.ComponentDiscoveryService; -import org.thingsboard.server.service.edge.instructions.EdgeInstallService; -import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.entitiy.TbNotificationEntityService; import org.thingsboard.server.service.entitiy.user.TbUserSettingsService; import org.thingsboard.server.service.ota.OtaPackageStateService; @@ -301,12 +297,6 @@ public abstract class BaseController { @Autowired(required = false) protected EdgeService edgeService; - @Autowired(required = false) - protected EdgeRpcService edgeRpcService; - - @Autowired(required = false) - protected EdgeInstallService edgeInstallService; - @Autowired protected TbNotificationEntityService notificationEntityService; @@ -824,18 +814,6 @@ public abstract class BaseController { } } - protected void sendEntityNotificationMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType action) { - sendNotificationMsgToEdge(tenantId, null, entityId, null, null, action); - } - - protected void sendEntityAssignToEdgeNotificationMsg(TenantId tenantId, EdgeId edgeId, EntityId entityId, EdgeEventActionType action) { - sendNotificationMsgToEdge(tenantId, edgeId, entityId, null, null, action); - } - - private void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) { - tbClusterService.sendNotificationMsgToEdge(tenantId, edgeId, entityId, body, type, action); - } - protected void processDashboardIdFromAdditionalInfo(ObjectNode additionalInfo, String requiredFields) throws ThingsboardException { String dashboardId = additionalInfo.has(requiredFields) ? additionalInfo.get(requiredFields).asText() : null; if (dashboardId != null && !dashboardId.equals("null")) { diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java index 9a021643cc..a50f9c7658 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java @@ -59,6 +59,8 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.EdgeBulkImportService; +import org.thingsboard.server.service.edge.instructions.EdgeInstallService; +import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.entitiy.edge.TbEdgeService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; @@ -67,6 +69,7 @@ import org.thingsboard.server.service.security.permission.Resource; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -94,8 +97,11 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI @RequestMapping("/api") @RequiredArgsConstructor public class EdgeController extends BaseController { + private final EdgeBulkImportService edgeBulkImportService; private final TbEdgeService tbEdgeService; + private final Optional edgeRpcServiceOpt; + private final Optional edgeInstallServiceOpt; public static final String EDGE_ID = "edgeId"; public static final String EDGE_SECURITY_CHECK = "If the user has the authority of 'Tenant Administrator', the server checks that the edge is owned by the same tenant. " + @@ -497,13 +503,13 @@ public class EdgeController extends BaseController { @PathVariable("edgeId") String strEdgeId) throws ThingsboardException { checkParameter("edgeId", strEdgeId); final DeferredResult response = new DeferredResult<>(); - if (isEdgesEnabled()) { + if (isEdgesEnabled() && edgeRpcServiceOpt.isPresent()) { EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); edgeId = checkNotNull(edgeId); SecurityUser user = getCurrentUser(); TenantId tenantId = user.getTenantId(); ToEdgeSyncRequest request = new ToEdgeSyncRequest(UUID.randomUUID(), tenantId, edgeId); - edgeRpcService.processSyncRequest(request, fromEdgeSyncResponse -> reply(response, fromEdgeSyncResponse)); + edgeRpcServiceOpt.get().processSyncRequest(request, fromEdgeSyncResponse -> reply(response, fromEdgeSyncResponse)); } else { throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL); } @@ -557,9 +563,13 @@ public class EdgeController extends BaseController { @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("edgeId") String strEdgeId, HttpServletRequest request) throws ThingsboardException { - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - edgeId = checkNotNull(edgeId); - Edge edge = checkEdgeId(edgeId, Operation.READ); - return checkNotNull(edgeInstallService.getDockerInstallInstructions(getTenantId(), edge, request)); + if (isEdgesEnabled() && edgeInstallServiceOpt.isPresent()) { + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + edgeId = checkNotNull(edgeId); + Edge edge = checkEdgeId(edgeId, Operation.READ); + return checkNotNull(edgeInstallServiceOpt.get().getDockerInstallInstructions(getTenantId(), edge, request)); + } else { + throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL); + } } } diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java index d1ecd0441a..9a727523d7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java @@ -28,7 +28,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetTypeId; @@ -107,9 +106,6 @@ public class WidgetTypeController extends AutoCommitController { } } - sendEntityNotificationMsg(getTenantId(), savedWidgetTypeDetails.getId(), - widgetTypeDetails.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); - return checkNotNull(savedWidgetTypeDetails); } @@ -133,8 +129,6 @@ public class WidgetTypeController extends AutoCommitController { autoCommit(currentUser, widgetsBundle.getId()); } } - - sendEntityNotificationMsg(getTenantId(), widgetTypeId, EdgeEventActionType.DELETED); } @ApiOperation(value = "Get all Widget types for specified Bundle (getBundleWidgetTypes)", diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 8a68dc21cb..e1f20be0e4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -33,10 +33,10 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.config.WebSocketConfiguration; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.dao.util.limits.RateLimitService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java index ed641c2d4f..0cd581b2e5 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java @@ -16,7 +16,6 @@ package org.thingsboard.server.exception; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java index 6c855a89c9..99884b013d 100644 --- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java +++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java @@ -237,10 +237,6 @@ public class EntityActionService { auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo); } - public void sendEntityNotificationMsgToEdge(TenantId tenantId, EntityId entityId, EdgeEventActionType action) { - tbClusterService.sendNotificationMsgToEdge(tenantId, null, entityId, null, null, action); - } - private T extractParameter(Class clazz, int index, Object... additionalInfo) { T result = null; if (additionalInfo != null && additionalInfo.length > index) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java index 110760acbc..1f38f83492 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.edge; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; @@ -23,22 +22,18 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; -import org.thingsboard.server.common.data.id.EdgeId; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeService; +import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessor; @@ -74,12 +69,6 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { @Autowired private EdgeService edgeService; - @Autowired - private EdgeEventService edgeEventService; - - @Autowired - private TbClusterService clusterService; - @Autowired private EdgeProcessor edgeProcessor; @@ -128,6 +117,9 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { @Autowired private RelationEdgeProcessor relationProcessor; + @Autowired + protected ApplicationEventPublisher eventPublisher; + private ExecutorService dbCallBackExecutor; @PostConstruct @@ -143,32 +135,16 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { } @Override - public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws Exception { + public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) { edge.setRootRuleChainId(ruleChainId); Edge savedEdge = edgeService.saveEdge(edge); ObjectNode isRootBody = JacksonUtil.newObjectNode(); isRootBody.put(EDGE_IS_ROOT_BODY_KEY, Boolean.TRUE); - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, isRootBody).get(); + eventPublisher.publishEvent(ActionEntityEvent.builder().tenantId(tenantId).edgeId(edge.getId()).entityId(ruleChainId) + .body(JacksonUtil.toString(isRootBody)).actionType(ActionType.UPDATED).build()); return savedEdge; } - private ListenableFuture saveEdgeEvent(TenantId tenantId, - EdgeId edgeId, - EdgeEventType type, - EdgeEventActionType action, - EntityId entityId, - JsonNode body) { - log.debug("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]", - tenantId, edgeId, type, action, entityId, body); - - EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body); - - return Futures.transform(edgeEventService.saveAsync(edgeEvent), unused -> { - clusterService.onEdgeEventUpdate(tenantId, edgeId); - return null; - }, dbCallBackExecutor); - } - @Override public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) { log.debug("Pushing notification to edge {}", edgeNotificationMsg); @@ -181,43 +157,43 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { future = edgeProcessor.processEdgeNotification(tenantId, edgeNotificationMsg); break; case ASSET: - future = assetProcessor.processAssetNotification(tenantId, edgeNotificationMsg); + future = assetProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case DEVICE: - future = deviceProcessor.processDeviceNotification(tenantId, edgeNotificationMsg); + future = deviceProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case ENTITY_VIEW: - future = entityViewProcessor.processEntityViewNotification(tenantId, edgeNotificationMsg); + future = entityViewProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case DASHBOARD: - future = dashboardProcessor.processDashboardNotification(tenantId, edgeNotificationMsg); + future = dashboardProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case RULE_CHAIN: - future = ruleChainProcessor.processRuleChainNotification(tenantId, edgeNotificationMsg); + future = ruleChainProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case USER: - future = userProcessor.processUserNotification(tenantId, edgeNotificationMsg); + future = userProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case CUSTOMER: future = customerProcessor.processCustomerNotification(tenantId, edgeNotificationMsg); break; case DEVICE_PROFILE: - future = deviceProfileProcessor.processDeviceProfileNotification(tenantId, edgeNotificationMsg); + future = deviceProfileProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case ASSET_PROFILE: - future = assetProfileProcessor.processAssetProfileNotification(tenantId, edgeNotificationMsg); + future = assetProfileProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case OTA_PACKAGE: - future = otaPackageProcessor.processOtaPackageNotification(tenantId, edgeNotificationMsg); + future = otaPackageProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case WIDGETS_BUNDLE: - future = widgetBundleProcessor.processWidgetsBundleNotification(tenantId, edgeNotificationMsg); + future = widgetBundleProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case WIDGET_TYPE: - future = widgetTypeProcessor.processWidgetTypeNotification(tenantId, edgeNotificationMsg); + future = widgetTypeProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case QUEUE: - future = queueProcessor.processQueueNotification(tenantId, edgeNotificationMsg); + future = queueProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case ALARM: future = alarmProcessor.processAlarmNotification(tenantId, edgeNotificationMsg); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java new file mode 100644 index 0000000000..43b05094a4 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -0,0 +1,158 @@ +/** + * 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.service.edge; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionalEventListener; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.edge.EdgeSynchronizationManager; +import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.RelationActionEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; + +import javax.annotation.PostConstruct; + +import static org.thingsboard.server.service.entitiy.DefaultTbNotificationEntityService.edgeTypeByActionType; + + +/** + * This event listener does not support async event processing because relay on ThreadLocal + * Another possible approach is to implement a special annotation and a bunch of classes similar to TransactionalApplicationListener + * This class is the simplest approach to maintain edge synchronization within the single class. + *

+ * For async event publishers, you have to decide whether publish event on creating async task in the same thread where dao method called + * @Autowired + * EdgeEventSynchronizationManager edgeSynchronizationManager + * ... + * //some async write action make future + * if (!edgeSynchronizationManager.isSync()) { + * future.addCallback(eventPublisher.publishEvent(...)) + * } + * */ +@Component +@RequiredArgsConstructor +@Slf4j +public class EdgeEventSourcingListener { + + private final TbClusterService tbClusterService; + private final EdgeSynchronizationManager edgeSynchronizationManager; + + @PostConstruct + public void init() { + log.info("EdgeEventSourcingListener initiated"); + } + + @TransactionalEventListener(fallbackExecution = true) + public void handleEvent(SaveEntityEvent event) { + if (edgeSynchronizationManager.isSync()) { + return; + } + try { + if (!isValidEdgeEventEntity(event.getEntity())) { + return; + } + log.trace("[{}] SaveEntityEvent called: {}", event.getTenantId(), event); + EdgeEventActionType action = Boolean.TRUE.equals(event.getAdded()) ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED; + tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), null, event.getEntityId(), + null, null, action); + } catch (Exception e) { + log.error("[{}] failed to process SaveEntityEvent: {}", event.getTenantId(), event); + } + } + + @TransactionalEventListener(fallbackExecution = true) + public void handleEvent(DeleteEntityEvent event) { + if (edgeSynchronizationManager.isSync()) { + return; + } + try { + log.trace("[{}] DeleteEntityEvent called: {}", event.getTenantId(), event); + tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), event.getEdgeId(), event.getEntityId(), + JacksonUtil.toString(event.getEntity()), null, EdgeEventActionType.DELETED); + } catch (Exception e) { + log.error("[{}] failed to process DeleteEntityEvent: {}", event.getTenantId(), event); + } + } + + @TransactionalEventListener(fallbackExecution = true) + public void handleEvent(ActionEntityEvent event) { + if (edgeSynchronizationManager.isSync()) { + return; + } + try { + log.trace("[{}] ActionEntityEvent called: {}", event.getTenantId(), event); + tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), event.getEdgeId(), event.getEntityId(), + event.getBody(), null, edgeTypeByActionType(event.getActionType())); + } catch (Exception e) { + log.error("[{}] failed to process ActionEntityEvent: {}", event.getTenantId(), event); + } + } + + @TransactionalEventListener(fallbackExecution = true) + public void handleEvent(RelationActionEvent event) { + if (edgeSynchronizationManager.isSync()) { + return; + } + try { + EntityRelation relation = event.getRelation(); + if (relation == null) { + log.trace("[{}] skipping RelationActionEvent event in case relation is null: {}", event.getTenantId(), event); + return; + } + if (!RelationTypeGroup.COMMON.equals(relation.getTypeGroup())) { + log.trace("[{}] skipping RelationActionEvent event in case NOT COMMON relation type group: {}", event.getTenantId(), event); + return; + } + log.trace("[{}] RelationActionEvent called: {}", event.getTenantId(), event); + tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), null, null, + JacksonUtil.toString(relation), EdgeEventType.RELATION, edgeTypeByActionType(event.getActionType())); + } catch (Exception e) { + log.error("[{}] failed to process RelationActionEvent: {}", event.getTenantId(), event); + } + } + + private boolean isValidEdgeEventEntity(Object entity) { + if (entity instanceof OtaPackageInfo) { + OtaPackageInfo otaPackageInfo = (OtaPackageInfo) entity; + return otaPackageInfo.hasUrl() || otaPackageInfo.isHasData(); + } else if (entity instanceof RuleChain) { + RuleChain ruleChain = (RuleChain) entity; + return RuleChainType.EDGE.equals(ruleChain.getType()); + } else if (entity instanceof User) { + User user = (User) entity; + return !Authority.SYS_ADMIN.equals(user.getAuthority()); + } else if (entity instanceof AlarmApiCallResult) { + AlarmApiCallResult alarmApiCallResult = (AlarmApiCallResult) entity; + return alarmApiCallResult.isModified(); + } + // Default: If the entity doesn't match any of the conditions, consider it as valid. + return true; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index ac7c791338..56f4f06fa3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EdgeUtils; @@ -55,6 +56,7 @@ import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeService; +import org.thingsboard.server.dao.edge.EdgeSynchronizationManager; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; @@ -249,15 +251,18 @@ public abstract class BaseEdgeProcessor { @Autowired protected QueueMsgConstructor queueMsgConstructor; + @Autowired + protected EdgeSynchronizationManager edgeSynchronizationManager; + @Autowired protected DbCallbackExecutorService dbCallbackExecutorService; protected ListenableFuture saveEdgeEvent(TenantId tenantId, - EdgeId edgeId, - EdgeEventType type, - EdgeEventActionType action, - EntityId entityId, - JsonNode body) { + EdgeId edgeId, + EdgeEventType type, + EdgeEventActionType action, + EntityId entityId, + JsonNode body) { log.debug("Pushing event to edge queue. tenantId [{}], edgeId [{}], type[{}], " + "action [{}], entityId [{}], body [{}]", tenantId, edgeId, type, action, entityId, body); @@ -288,7 +293,7 @@ public abstract class BaseEdgeProcessor { return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); } - protected List> processActionForAllEdgesByTenantId(TenantId tenantId, + private List> processActionForAllEdgesByTenantId(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId, @@ -340,36 +345,46 @@ public abstract class BaseEdgeProcessor { } } - protected ListenableFuture processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); + public ListenableFuture processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); - EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, - new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); - EdgeId edgeId = safeGetEdgeId(edgeNotificationMsg); - switch (actionType) { - case ADDED: - case UPDATED: - case CREDENTIALS_UPDATED: - case ASSIGNED_TO_CUSTOMER: - case UNASSIGNED_FROM_CUSTOMER: - case DELETED: - if (edgeId != null) { - return saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null); - } else { - return pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType); - } - case ASSIGNED_TO_EDGE: - case UNASSIGNED_FROM_EDGE: - ListenableFuture future = saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null); - return Futures.transformAsync(future, unused -> { - if (type.equals(EdgeEventType.RULE_CHAIN)) { - return updateDependentRuleChains(tenantId, new RuleChainId(entityId.getId()), edgeId); + EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); + EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); + if (type.isAllEdgesRelated()) { + return processEntityNotificationForAllEdges(tenantId, type, actionType, entityId); + } else { + JsonNode body = JacksonUtil.toJsonNode(edgeNotificationMsg.getBody()); + EdgeId edgeId = safeGetEdgeId(edgeNotificationMsg); + switch (actionType) { + case UPDATED: + case CREDENTIALS_UPDATED: + case ASSIGNED_TO_CUSTOMER: + case UNASSIGNED_FROM_CUSTOMER: + if (edgeId != null) { + return saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, body); } else { - return Futures.immediateFuture(null); + return processNotificationToRelatedEdges(tenantId, entityId, type, actionType); } - }, dbCallbackExecutorService); - default: - return Futures.immediateFuture(null); + case DELETED: + EdgeEventActionType deleted = EdgeEventActionType.DELETED; + if (edgeId != null) { + return saveEdgeEvent(tenantId, edgeId, type, deleted, entityId, body); + } else { + return Futures.transform(Futures.allAsList(processActionForAllEdgesByTenantId(tenantId, type, deleted, entityId, body)), + voids -> null, dbCallbackExecutorService); + } + case ASSIGNED_TO_EDGE: + case UNASSIGNED_FROM_EDGE: + ListenableFuture future = saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, body); + return Futures.transformAsync(future, unused -> { + if (type.equals(EdgeEventType.RULE_CHAIN)) { + return updateDependentRuleChains(tenantId, new RuleChainId(entityId.getId()), edgeId); + } else { + return Futures.immediateFuture(null); + } + }, dbCallbackExecutorService); + default: + return Futures.immediateFuture(null); + } } } @@ -381,7 +396,7 @@ public abstract class BaseEdgeProcessor { } } - private ListenableFuture pushNotificationToAllRelatedEdges(TenantId tenantId, EntityId entityId, EdgeEventType type, EdgeEventActionType actionType) { + private ListenableFuture processNotificationToRelatedEdges(TenantId tenantId, EntityId entityId, EdgeEventType type, EdgeEventActionType actionType) { PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageData pageData; List> futures = new ArrayList<>(); @@ -432,10 +447,7 @@ public abstract class BaseEdgeProcessor { return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); } - protected ListenableFuture processEntityNotificationForAllEdges(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); - EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); - EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); + private ListenableFuture processEntityNotificationForAllEdges(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId) { switch (actionType) { case ADDED: case UPDATED: diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java index 189d69e68d..0b86352c02 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.alarm; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -28,6 +29,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -62,9 +64,9 @@ public class AlarmEdgeProcessor extends BaseAlarmProcessor { AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); switch (actionType) { case DELETED: - EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); Alarm deletedAlarm = JacksonUtil.OBJECT_MAPPER.readValue(edgeNotificationMsg.getBody(), Alarm.class); - return saveEdgeEvent(tenantId, edgeId, EdgeEventType.ALARM, actionType, alarmId, JacksonUtil.OBJECT_MAPPER.valueToTree(deletedAlarm)); + List> delFutures = pushEventToAllRelatedEdges(tenantId, deletedAlarm.getOriginator(), alarmId, actionType, JacksonUtil.OBJECT_MAPPER.valueToTree(deletedAlarm)); + return Futures.transform(Futures.allAsList(delFutures), voids -> null, dbCallbackExecutorService); default: ListenableFuture alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); return Futures.transformAsync(alarmFuture, alarm -> { @@ -75,28 +77,33 @@ public class AlarmEdgeProcessor extends BaseAlarmProcessor { if (type == null) { return Futures.immediateFuture(null); } - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - List> futures = new ArrayList<>(); - do { - pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator(), pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - for (EdgeId relatedEdgeId : pageData.getData()) { - futures.add(saveEdgeEvent(tenantId, - relatedEdgeId, - EdgeEventType.ALARM, - EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), - alarmId, - null)); - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - } while (pageData != null && pageData.hasNext()); + List> futures = pushEventToAllRelatedEdges(tenantId, alarm.getOriginator(), alarmId, actionType, null); return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); }, dbCallbackExecutorService); } } + private List> pushEventToAllRelatedEdges(TenantId tenantId, EntityId originatorId, AlarmId alarmId, EdgeEventActionType actionType, JsonNode body) { + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData pageData; + List> futures = new ArrayList<>(); + do { + pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, originatorId, pageLink); + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { + for (EdgeId relatedEdgeId : pageData.getData()) { + futures.add(saveEdgeEvent(tenantId, + relatedEdgeId, + EdgeEventType.ALARM, + actionType, + alarmId, + body)); + } + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } + } while (pageData != null && pageData.hasNext()); + return futures; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java index be64f475a2..fbe141ab32 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java @@ -49,6 +49,7 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { return Futures.immediateFuture(null); } try { + edgeSynchronizationManager.getSync().set(true); switch (alarmUpdateMsg.getMsgType()) { case ENTITY_CREATED_RPC_MESSAGE: case ENTITY_UPDATED_RPC_MESSAGE: @@ -72,26 +73,26 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { } else { alarmService.updateAlarm(AlarmUpdateRequest.fromAlarm(alarm)); } - return Futures.immediateFuture(null); + break; case ALARM_ACK_RPC_MESSAGE: Alarm alarmToAck = alarmService.findAlarmById(tenantId, alarmId); if (alarmToAck != null) { alarmService.acknowledgeAlarm(tenantId, alarmId, alarmUpdateMsg.getAckTs()); } - return Futures.immediateFuture(null); + break; case ALARM_CLEAR_RPC_MESSAGE: Alarm alarmToClear = alarmService.findAlarmById(tenantId, alarmId); if (alarmToClear != null) { alarmService.clearAlarm(tenantId, alarmId, alarmUpdateMsg.getClearTs(), JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); } - return Futures.immediateFuture(null); + break; case ENTITY_DELETED_RPC_MESSAGE: Alarm alarmToDelete = alarmService.findAlarmById(tenantId, alarmId); if (alarmToDelete != null) { alarmService.delAlarm(tenantId, alarmId); } - return Futures.immediateFuture(null); + break; case UNRECOGNIZED: default: return handleUnsupportedMsgType(alarmUpdateMsg.getMsgType()); @@ -99,7 +100,10 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { } catch (Exception e) { log.error("[{}] Failed to process alarm update msg [{}]", tenantId, alarmUpdateMsg, e); return Futures.immediateFailedFuture(e); + } finally { + edgeSynchronizationManager.getSync().remove(); } + return Futures.immediateFuture(null); } private EntityId getAlarmOriginator(TenantId tenantId, String entityName, EntityType entityType) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java index 2fb7320219..d7824a7467 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.edge.rpc.processor.asset; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; @@ -23,11 +22,9 @@ import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @@ -72,8 +69,4 @@ public class AssetEdgeProcessor extends BaseEdgeProcessor { } return downlinkMsg; } - - public ListenableFuture processAssetNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - return processEntityNotification(tenantId, edgeNotificationMsg); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java index 51c42e5764..ec0e0b9761 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProfileEdgeProcessor.java @@ -15,18 +15,15 @@ */ package org.thingsboard.server.service.edge.rpc.processor.asset; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.AssetProfileId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @@ -63,9 +60,4 @@ public class AssetProfileEdgeProcessor extends BaseEdgeProcessor { } return downlinkMsg; } - - public ListenableFuture processAssetProfileNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java index 9651609bd3..14f566db0a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java @@ -15,18 +15,15 @@ */ package org.thingsboard.server.service.edge.rpc.processor.dashboard; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @@ -67,8 +64,4 @@ public class DashboardEdgeProcessor extends BaseEdgeProcessor { } return downlinkMsg; } - - public ListenableFuture processDashboardNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - return processEntityNotification(tenantId, edgeNotificationMsg); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java index 742acdbcda..1421cf32c0 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java @@ -97,7 +97,7 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { deviceCredentials.setCredentialsId(StringUtils.randomAlphanumeric(20)); deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); } - tbClusterService.onDeviceUpdated(savedDevice, created ? null : device, false); + tbClusterService.onDeviceUpdated(savedDevice, created ? null : device); } finally { deviceCreationLock.unlock(); } @@ -113,6 +113,8 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { log.debug("Updating device credentials for device [{}]. New device credentials Id [{}], value [{}]", device.getName(), deviceCredentialsUpdateMsg.getCredentialsId(), deviceCredentialsUpdateMsg.getCredentialsValue()); try { + edgeSynchronizationManager.getSync().set(true); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId()); deviceCredentials.setCredentialsType(DeviceCredentialsType.valueOf(deviceCredentialsUpdateMsg.getCredentialsType())); deviceCredentials.setCredentialsId(deviceCredentialsUpdateMsg.getCredentialsId()); @@ -123,6 +125,8 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { log.error("Can't update device credentials for device [{}], deviceCredentialsUpdateMsg [{}]", device.getName(), deviceCredentialsUpdateMsg, e); throw new RuntimeException(e); + } finally { + edgeSynchronizationManager.getSync().remove(); } } else { log.warn("Can't find device by id [{}], deviceCredentialsUpdateMsg [{}]", deviceId, deviceCredentialsUpdateMsg); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java index 67194cb618..d083c5b16f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java @@ -54,7 +54,6 @@ import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -71,6 +70,8 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { log.trace("[{}] executing processDeviceMsgFromEdge [{}] from edge [{}]", tenantId, deviceUpdateMsg, edge.getName()); DeviceId deviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())); try { + edgeSynchronizationManager.getSync().set(true); + switch (deviceUpdateMsg.getMsgType()) { case ENTITY_CREATED_RPC_MESSAGE: case ENTITY_UPDATED_RPC_MESSAGE: @@ -93,6 +94,8 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { } else { return Futures.immediateFailedFuture(e); } + } finally { + edgeSynchronizationManager.getSync().remove(); } } @@ -308,8 +311,4 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor { .addDeviceCredentialsRequestMsg(deviceCredentialsRequestMsg); return builder.build(); } - - public ListenableFuture processDeviceNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - return processEntityNotification(tenantId, edgeNotificationMsg); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java index 5ddfecfdb1..c888ec2925 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProfileEdgeProcessor.java @@ -15,18 +15,15 @@ */ package org.thingsboard.server.service.edge.rpc.processor.device; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @@ -63,8 +60,4 @@ public class DeviceProfileEdgeProcessor extends BaseEdgeProcessor { } return downlinkMsg; } - - public ListenableFuture processDeviceProfileNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java index 29965fcc69..0964a434ba 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java @@ -15,18 +15,15 @@ */ package org.thingsboard.server.service.edge.rpc.processor.entityview; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @@ -67,8 +64,4 @@ public class EntityViewEdgeProcessor extends BaseEdgeProcessor { } return downlinkMsg; } - - public ListenableFuture processEntityViewNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - return processEntityNotification(tenantId, edgeNotificationMsg); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java index 8206e0f1b0..fae6399e3a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java @@ -15,18 +15,15 @@ */ package org.thingsboard.server.service.edge.rpc.processor.ota; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.OtaPackageId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @@ -63,8 +60,4 @@ public class OtaPackageEdgeProcessor extends BaseEdgeProcessor { } return downlinkMsg; } - - public ListenableFuture processOtaPackageNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java index cbacaf9276..8562582940 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java @@ -15,18 +15,15 @@ */ package org.thingsboard.server.service.edge.rpc.processor.queue; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.QueueId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @@ -64,7 +61,4 @@ public class QueueEdgeProcessor extends BaseEdgeProcessor { return downlinkMsg; } - public ListenableFuture processQueueNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java index df683bbbe2..9038cc9c33 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java @@ -34,8 +34,9 @@ import java.util.UUID; public abstract class BaseRelationProcessor extends BaseEdgeProcessor { public ListenableFuture processRelationMsg(TenantId tenantId, RelationUpdateMsg relationUpdateMsg) { - log.trace("[{}] processRelationFromEdge [{}]", tenantId, relationUpdateMsg); + log.trace("[{}] processRelationMsg [{}]", tenantId, relationUpdateMsg); try { + edgeSynchronizationManager.getSync().set(true); EntityRelation entityRelation = new EntityRelation(); UUID fromUUID = new UUID(relationUpdateMsg.getFromIdMSB(), relationUpdateMsg.getFromIdLSB()); @@ -55,15 +56,15 @@ public abstract class BaseRelationProcessor extends BaseEdgeProcessor { case ENTITY_UPDATED_RPC_MESSAGE: if (isEntityExists(tenantId, entityRelation.getTo()) && isEntityExists(tenantId, entityRelation.getFrom())) { - return Futures.transform(relationService.saveRelationAsync(tenantId, entityRelation), - (result) -> null, dbCallbackExecutorService); + relationService.saveRelation(tenantId, entityRelation); + break; } else { log.warn("Skipping relating update msg because from/to entity doesn't exists on edge, {}", relationUpdateMsg); - return Futures.immediateFuture(null); + break; } case ENTITY_DELETED_RPC_MESSAGE: - return Futures.transform(relationService.deleteRelationAsync(tenantId, entityRelation), - (result) -> null, dbCallbackExecutorService); + relationService.deleteRelation(tenantId, entityRelation); + break; case UNRECOGNIZED: default: return handleUnsupportedMsgType(relationUpdateMsg.getMsgType()); @@ -71,6 +72,9 @@ public abstract class BaseRelationProcessor extends BaseEdgeProcessor { } catch (Exception e) { log.error("[{}] Failed to process relation update msg [{}]", tenantId, relationUpdateMsg, e); return Futures.immediateFailedFuture(e); + } finally { + edgeSynchronizationManager.getSync().remove(); } + return Futures.immediateFuture(null); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java index 69d4f7ef64..ba6fc1ef97 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java @@ -15,13 +15,11 @@ */ package org.thingsboard.server.service.edge.rpc.processor.rule; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; @@ -29,7 +27,6 @@ import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @@ -93,8 +90,4 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor { } return downlinkMsg; } - - public ListenableFuture processRuleChainNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - return processEntityNotification(tenantId, edgeNotificationMsg); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java index 9070686fd6..de40fdb1a3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java @@ -15,19 +15,16 @@ */ package org.thingsboard.server.service.edge.rpc.processor.user; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @@ -67,11 +64,9 @@ public class UserEdgeProcessor extends BaseEdgeProcessor { .addUserCredentialsUpdateMsg(userCredentialsUpdateMsg) .build(); } + break; } return downlinkMsg; } - public ListenableFuture processUserNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java index a429816a31..d50e752fdf 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java @@ -15,18 +15,15 @@ */ package org.thingsboard.server.service.edge.rpc.processor.widget; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @@ -63,8 +60,4 @@ public class WidgetBundleEdgeProcessor extends BaseEdgeProcessor { } return downlinkMsg; } - - public ListenableFuture processWidgetsBundleNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java index 3caad57081..5171724439 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java @@ -15,18 +15,15 @@ */ package org.thingsboard.server.service.edge.rpc.processor.widget; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @@ -64,7 +61,4 @@ public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor { return downlinkMsg; } - public ListenableFuture processWidgetTypeNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java index 68b9cdecbb..f2465c9398 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java @@ -22,28 +22,20 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmComment; -import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEventActionType; -import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -51,8 +43,6 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.service.action.EntityActionService; import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; -import java.util.List; - @Slf4j @Service @RequiredArgsConstructor @@ -98,54 +88,6 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS } } - @Override - public void notifyDeleteEntity(TenantId tenantId, I entityId, E entity, - CustomerId customerId, ActionType actionType, - List relatedEdgeIds, - User user, Object... additionalInfo) { - logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); - sendDeleteNotificationMsg(tenantId, entityId, relatedEdgeIds, null); - } - - @Override - public void notifyDeleteAlarm(TenantId tenantId, Alarm alarm, EntityId originatorId, CustomerId customerId, - List relatedEdgeIds, User user, String body, Object... additionalInfo) { - logEntityAction(tenantId, originatorId, alarm, customerId, ActionType.DELETED, user, additionalInfo); - sendAlarmDeleteNotificationMsg(tenantId, alarm, relatedEdgeIds, body); - } - - @Override - public void notifyDeleteRuleChain(TenantId tenantId, RuleChain ruleChain, List relatedEdgeIds, User user) { - RuleChainId ruleChainId = ruleChain.getId(); - logEntityAction(tenantId, ruleChainId, ruleChain, null, ActionType.DELETED, user, null, ruleChainId.toString()); - if (RuleChainType.EDGE.equals(ruleChain.getType())) { - sendDeleteNotificationMsg(tenantId, ruleChainId, relatedEdgeIds, null); - } - } - - @Override - public void notifySendMsgToEdgeService(TenantId tenantId, I entityId, EdgeEventActionType edgeEventActionType) { - sendEntityNotificationMsg(tenantId, entityId, edgeEventActionType); - } - - @Override - public void notifyAssignOrUnassignEntityToCustomer(TenantId tenantId, I entityId, - CustomerId customerId, E entity, - ActionType actionType, - User user, Object... additionalInfo) { - logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); - sendEntityNotificationMsg(tenantId, entityId, edgeTypeByActionType(actionType), JacksonUtil.toString(customerId)); - } - - @Override - public void notifyAssignOrUnassignEntityToEdge(TenantId tenantId, I entityId, - CustomerId customerId, EdgeId edgeId, - E entity, ActionType actionType, - User user, Object... additionalInfo) { - logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); - sendEntityAssignToEdgeNotificationMsg(tenantId, edgeId, entityId, edgeTypeByActionType(actionType)); - } - @Override public void notifyCreateOrUpdateTenant(Tenant tenant, ComponentLifecycleEvent event) { tbClusterService.onTenantChange(tenant, null); @@ -168,18 +110,16 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS @Override public void notifyDeleteDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, - List relatedEdgeIds, User user, Object... additionalInfo) { + User user, Object... additionalInfo) { gatewayNotificationsService.onDeviceDeleted(device); tbClusterService.onDeviceDeleted(device, null); - - notifyDeleteEntity(tenantId, deviceId, device, customerId, ActionType.DELETED, relatedEdgeIds, user, additionalInfo); + logEntityAction(tenantId, deviceId, device, customerId, ActionType.DELETED, user, additionalInfo); } @Override public void notifyUpdateDeviceCredentials(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, DeviceCredentials deviceCredentials, User user) { tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(tenantId, deviceCredentials.getDeviceId(), deviceCredentials), null); - sendEntityNotificationMsg(tenantId, deviceId, EdgeEventActionType.CREDENTIALS_UPDATED); logEntityAction(tenantId, deviceId, device, customerId, ActionType.CREDENTIALS_UPDATED, user, deviceCredentials); } @@ -190,16 +130,6 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS pushAssignedFromNotification(tenant, newTenantId, device); } - @Override - public void notifyCreateOrUpdateEntity(TenantId tenantId, I entityId, E entity, - CustomerId customerId, ActionType actionType, - User user, Object... additionalInfo) { - logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); - if (actionType == ActionType.UPDATED) { - sendEntityNotificationMsg(tenantId, entityId, EdgeEventActionType.UPDATED); - } - } - @Override public void notifyCreateOrUpdateOrDeleteEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType, User user, Object... additionalInfo) { @@ -222,65 +152,10 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS } @Override - public void notifyCreateOrUpdateAlarm(AlarmInfo alarm, ActionType actionType, User user, Object... additionalInfo) { - logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarm, alarm.getCustomerId(), actionType, user, additionalInfo); - sendEntityNotificationMsg(alarm.getTenantId(), alarm.getId(), edgeTypeByActionType(actionType)); - } - - @Override - public void notifyAlarmComment(Alarm alarm, AlarmComment alarmComment, ActionType actionType, User user) { - logEntityAction(alarm.getTenantId(), alarm.getId(), alarm, alarm.getCustomerId(), actionType, user, alarmComment); - } - - @Override - public void notifyCreateOrUpdateOrDelete(TenantId tenantId, CustomerId customerId, - I entityId, E entity, User user, - ActionType actionType, boolean sendNotifyMsgToEdge, Exception e, - Object... additionalInfo) { - logEntityAction(tenantId, entityId, entity, customerId, actionType, user, e, additionalInfo); - if (sendNotifyMsgToEdge) { - sendEntityNotificationMsg(tenantId, entityId, edgeTypeByActionType(actionType)); - } - } - - @Override - public void notifyRelation(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user, - ActionType actionType, Object... additionalInfo) { - logEntityAction(tenantId, relation.getFrom(), null, customerId, actionType, user, additionalInfo); - logEntityAction(tenantId, relation.getTo(), null, customerId, actionType, user, additionalInfo); - if (!EntityType.EDGE.equals(relation.getFrom().getEntityType()) && !EntityType.EDGE.equals(relation.getTo().getEntityType())) { - sendNotificationMsgToEdge(tenantId, null, null, JacksonUtil.toString(relation), - EdgeEventType.RELATION, edgeTypeByActionType(actionType)); - } - } - - private void sendEntityNotificationMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType action) { - sendEntityNotificationMsg(tenantId, entityId, action, null); - } - - private void sendEntityNotificationMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType action, String body) { - sendNotificationMsgToEdge(tenantId, null, entityId, body, null, action); - } - - private void sendAlarmDeleteNotificationMsg(TenantId tenantId, Alarm alarm, List edgeIds, String body) { - sendDeleteNotificationMsg(tenantId, alarm.getId(), edgeIds, body); - } - - private void sendDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List edgeIds, String body) { - if (edgeIds != null && !edgeIds.isEmpty()) { - for (EdgeId edgeId : edgeIds) { - sendNotificationMsgToEdge(tenantId, edgeId, entityId, body, null, EdgeEventActionType.DELETED); - } - } - } - - private void sendEntityAssignToEdgeNotificationMsg(TenantId tenantId, EdgeId edgeId, EntityId entityId, EdgeEventActionType action) { - sendNotificationMsgToEdge(tenantId, edgeId, entityId, null, null, action); - } - - private void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, - EdgeEventType type, EdgeEventActionType action) { - tbClusterService.sendNotificationMsgToEdge(tenantId, edgeId, entityId, body, type, action); + public void logEntityRelationAction(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user, + ActionType actionType, Exception e, Object... additionalInfo) { + logEntityAction(tenantId, relation.getFrom(), null, customerId, actionType, user, e, additionalInfo); + logEntityAction(tenantId, relation.getTo(), null, customerId, actionType, user, e, additionalInfo); } private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) { @@ -327,6 +202,8 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS return EdgeEventActionType.ASSIGNED_TO_EDGE; case UNASSIGNED_FROM_EDGE: return EdgeEventActionType.UNASSIGNED_FROM_EDGE; + case CREDENTIALS_UPDATED: + return EdgeEventActionType.CREDENTIALS_UPDATED; default: return null; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java index c5f8f85831..d14b2c6293 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java @@ -19,12 +19,8 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmComment; -import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; @@ -32,11 +28,8 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.security.DeviceCredentials; -import java.util.List; - public interface TbNotificationEntityService { void logEntityAction(TenantId tenantId, I entityId, ActionType actionType, User user, @@ -55,33 +48,6 @@ public interface TbNotificationEntityService { ActionType actionType, User user, Exception e, Object... additionalInfo); - void notifyCreateOrUpdateEntity(TenantId tenantId, I entityId, E entity, - CustomerId customerId, ActionType actionType, - User user, Object... additionalInfo); - - void notifyDeleteEntity(TenantId tenantId, I entityId, E entity, - CustomerId customerId, ActionType actionType, - List relatedEdgeIds, - User user, Object... additionalInfo); - - void notifyDeleteAlarm(TenantId tenantId, Alarm alarm, EntityId originatorId, CustomerId customerId, - List relatedEdgeIds, User user, String body, Object... additionalInfo); - - void notifyDeleteRuleChain(TenantId tenantId, RuleChain ruleChain, - List relatedEdgeIds, User user); - - void notifySendMsgToEdgeService(TenantId tenantId, I entityId, EdgeEventActionType edgeEventActionType); - - void notifyAssignOrUnassignEntityToCustomer(TenantId tenantId, I entityId, - CustomerId customerId, E entity, - ActionType actionType, - User user, Object... additionalInfo); - - void notifyAssignOrUnassignEntityToEdge(TenantId tenantId, I entityId, - CustomerId customerId, EdgeId edgeId, - E entity, ActionType actionType, - User user, Object... additionalInfo); - void notifyCreateOrUpdateTenant(Tenant tenant, ComponentLifecycleEvent event); void notifyDeleteTenant(Tenant tenant); @@ -90,7 +56,7 @@ public interface TbNotificationEntityService { Device oldDevice, ActionType actionType, User user, Object... additionalInfo); void notifyDeleteDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, - List relatedEdgeIds, User user, Object... additionalInfo); + User user, Object... additionalInfo); void notifyUpdateDeviceCredentials(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, DeviceCredentials deviceCredentials, User user); @@ -101,16 +67,6 @@ public interface TbNotificationEntityService { void notifyCreateOrUpdateOrDeleteEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType, User user, Object... additionalInfo); - void notifyCreateOrUpdateAlarm(AlarmInfo alarm, ActionType actionType, User user, Object... additionalInfo); - - void notifyAlarmComment(Alarm alarm, AlarmComment alarmComment, ActionType actionType, User user); - - - void notifyCreateOrUpdateOrDelete(TenantId tenantId, CustomerId customerId, - I entityId, E entity, User user, - ActionType actionType, boolean sendNotifyMsgToEdge, - Exception e, Object... additionalInfo); - - void notifyRelation(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user, - ActionType actionType, Object... additionalInfo); + void logEntityRelationAction(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user, + ActionType actionType, Exception e, Object... additionalInfo); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java index 6dfc81c747..282d11ab2a 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java @@ -45,7 +45,8 @@ public class DefaultTbAlarmCommentService extends AbstractTbEntityService implem } try { AlarmComment savedAlarmComment = checkNotNull(alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment)); - notificationEntityService.notifyAlarmComment(alarm, savedAlarmComment, actionType, user); + notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getId(), alarm, alarm.getCustomerId(), actionType, user, savedAlarmComment); + return savedAlarmComment; } catch (Exception e) { notificationEntityService.logEntityAction(alarm.getTenantId(), emptyId(EntityType.ALARM), alarm, actionType, user, e, alarmComment); @@ -62,7 +63,7 @@ public class DefaultTbAlarmCommentService extends AbstractTbEntityService implem String.format("User %s deleted his comment", (user.getFirstName() == null || user.getLastName() == null) ? user.getName() : user.getFirstName() + " " + user.getLastName()))); AlarmComment savedAlarmComment = checkNotNull(alarmCommentService.saveAlarmComment(alarm.getTenantId(), alarmComment)); - notificationEntityService.notifyAlarmComment(alarm, savedAlarmComment, ActionType.DELETED_COMMENT, user); + notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getId(), alarm, alarm.getCustomerId(), ActionType.DELETED_COMMENT, user, savedAlarmComment); } else { throw new ThingsboardException("System comment could not be deleted", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index 07c66e359a..e776c040c0 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -67,10 +67,6 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb if (!result.isSuccessful()) { throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); } - actionType = result.isCreated() ? ActionType.ADDED : ActionType.UPDATED; - if (result.isModified()) { - notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), actionType, user); - } AlarmInfo resultAlarm = result.getAlarm(); if (alarm.isAcknowledged() && !resultAlarm.isAcknowledged()) { resultAlarm = ack(resultAlarm, alarm.getAckTs(), user); @@ -85,6 +81,10 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } else if (newAssignee == null && curAssignee != null) { resultAlarm = unassign(alarm, alarm.getAssignTs(), user); } + if (result.isModified()) { + notificationEntityService.logEntityAction(tenantId, alarm.getOriginator(), resultAlarm, + resultAlarm.getCustomerId(), actionType, user); + } return new Alarm(resultAlarm); } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ALARM), alarm, actionType, user, e); @@ -103,6 +103,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb if (!result.isSuccessful()) { throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); } + AlarmInfo alarmInfo = result.getAlarm(); if (result.isModified()) { AlarmComment alarmComment = AlarmComment.builder() .alarmId(alarm.getId()) @@ -117,11 +118,12 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } catch (ThingsboardException e) { log.error("Failed to save alarm comment", e); } - notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ACK, user); + notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarmInfo, + alarmInfo.getCustomerId(), ActionType.ALARM_ACK, user); } else { throw new ThingsboardException("Alarm was already acknowledged!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } - return result.getAlarm(); + return alarmInfo; } @Override @@ -135,6 +137,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb if (!result.isSuccessful()) { throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); } + AlarmInfo alarmInfo = result.getAlarm(); if (result.isCleared()) { AlarmComment alarmComment = AlarmComment.builder() .alarmId(alarm.getId()) @@ -149,11 +152,12 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } catch (ThingsboardException e) { log.error("Failed to save alarm comment", e); } - notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_CLEAR, user); + notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarmInfo, + alarmInfo.getCustomerId(), ActionType.ALARM_CLEAR, user); } else { throw new ThingsboardException("Alarm was already cleared!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } - return result.getAlarm(); + return alarmInfo; } @Override @@ -180,7 +184,8 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } catch (ThingsboardException e) { log.error("Failed to save alarm comment", e); } - notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ASSIGNED, user); + notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarmInfo, + alarmInfo.getCustomerId(), ActionType.ALARM_ASSIGNED, user); } else { throw new ThingsboardException("Alarm was already assigned to this user!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } @@ -208,7 +213,8 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } catch (ThingsboardException e) { log.error("Failed to save alarm comment", e); } - notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_UNASSIGNED, user); + notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarmInfo, + alarmInfo.getCustomerId(), ActionType.ALARM_UNASSIGNED, user); } else { throw new ThingsboardException("Alarm was already unassigned!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } @@ -239,7 +245,8 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } catch (ThingsboardException e) { log.error("Failed to save alarm comment", e); } - notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_UNASSIGNED, user); + notificationEntityService.logEntityAction(alarm.getTenantId(), alarm.getOriginator(), result.getAlarm(), + alarm.getCustomerId(), ActionType.ALARM_UNASSIGNED, user); } } @@ -251,9 +258,8 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb @Override public Boolean delete(Alarm alarm, User user) { TenantId tenantId = alarm.getTenantId(); - List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, alarm.getOriginator()); - notificationEntityService.notifyDeleteAlarm(tenantId, alarm, alarm.getOriginator(), alarm.getCustomerId(), - relatedEdgeIds, user, JacksonUtil.toString(alarm)); + notificationEntityService.logEntityAction(tenantId, alarm.getOriginator(), alarm, alarm.getCustomerId(), + ActionType.DELETED, user); return alarmSubscriptionService.deleteAlarm(tenantId, alarm.getId()); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java index ab57ecd050..6510ecfb7f 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java @@ -36,8 +36,6 @@ import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.profile.TbAssetProfileCache; -import java.util.List; - import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE; @Service @@ -62,8 +60,8 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb } Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); autoCommit(user, savedAsset.getId()); - notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedAsset.getId(), savedAsset, - asset.getCustomerId(), actionType, user); + notificationEntityService.logEntityAction(tenantId, savedAsset.getId(), savedAsset, asset.getCustomerId(), + actionType, user); tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedAsset.getId(), asset.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); return savedAsset; @@ -75,17 +73,16 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb @Override public ListenableFuture delete(Asset asset, User user) { + ActionType actionType = ActionType.DELETED; TenantId tenantId = asset.getTenantId(); AssetId assetId = asset.getId(); try { - List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, assetId); assetService.deleteAsset(tenantId, assetId); - notificationEntityService.notifyDeleteEntity(tenantId, assetId, asset, asset.getCustomerId(), - ActionType.DELETED, relatedEdgeIds, user, assetId.toString()); + notificationEntityService.logEntityAction(tenantId, assetId, asset, asset.getCustomerId(), actionType, user, assetId.toString()); tbClusterService.broadcastEntityStateChangeEvent(tenantId, assetId, ComponentLifecycleEvent.DELETED); return removeAlarmsByEntityId(tenantId, assetId); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), ActionType.DELETED, user, e, + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, assetId.toString()); throw e; } @@ -97,8 +94,8 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb CustomerId customerId = customer.getId(); try { Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(tenantId, assetId, customerId)); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, customerId, savedAsset, - actionType, user, assetId.toString(), customerId.toString(), customer.getName()); + notificationEntityService.logEntityAction(tenantId, assetId, savedAsset, customerId, actionType, user, + assetId.toString(), customerId.toString(), customer.getName()); return savedAsset; } catch (Exception e) { @@ -114,8 +111,8 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb try { Asset savedAsset = checkNotNull(assetService.unassignAssetFromCustomer(tenantId, assetId)); CustomerId customerId = customer.getId(); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, customerId, savedAsset, - actionType, user, assetId.toString(), customerId.toString(), customer.getName()); + notificationEntityService.logEntityAction(tenantId, assetId, savedAsset, customerId, actionType, user, + assetId.toString(), customerId.toString(), customer.getName()); return savedAsset; } catch (Exception e) { @@ -130,8 +127,9 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb try { Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(tenantId, assetId, publicCustomer.getId())); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, savedAsset.getCustomerId(), savedAsset, - actionType, user, assetId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); + CustomerId customerId = publicCustomer.getId(); + notificationEntityService.logEntityAction(tenantId, assetId, savedAsset, customerId, actionType, user, + assetId.toString(), customerId.toString(), publicCustomer.getName()); return savedAsset; } catch (Exception e) { @@ -146,9 +144,8 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb EdgeId edgeId = edge.getId(); try { Asset savedAsset = checkNotNull(assetService.assignAssetToEdge(tenantId, assetId, edgeId)); - notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, assetId, savedAsset.getCustomerId(), - edgeId, savedAsset, actionType, user, assetId.toString(), edgeId.toString(), edge.getName()); - + notificationEntityService.logEntityAction(tenantId, assetId, savedAsset, savedAsset.getCustomerId(), + actionType, user, assetId.toString(), edgeId.toString(), edge.getName()); return savedAsset; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, @@ -164,9 +161,8 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb EdgeId edgeId = edge.getId(); try { Asset savedAsset = checkNotNull(assetService.unassignAssetFromEdge(tenantId, assetId, edgeId)); - - notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, assetId, asset.getCustomerId(), - edgeId, asset, actionType, user, assetId.toString(), edgeId.toString(), edge.getName()); + notificationEntityService.logEntityAction(tenantId, assetId, asset, asset.getCustomerId(), + actionType, user, assetId.toString(), edgeId.toString(), edge.getName()); return savedAsset; } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java index b662994451..00b181a52a 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java @@ -59,8 +59,8 @@ public class DefaultTbAssetProfileService extends AbstractTbEntityService implem tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedAssetProfile.getId(), actionType.equals(ActionType.ADDED) ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedAssetProfile.getId(), - savedAssetProfile, user, actionType, true, null); + notificationEntityService.logEntityAction(tenantId, savedAssetProfile.getId(), savedAssetProfile, + null, actionType, user); return savedAssetProfile; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET_PROFILE), assetProfile, actionType, user, e); @@ -70,16 +70,17 @@ public class DefaultTbAssetProfileService extends AbstractTbEntityService implem @Override public void delete(AssetProfile assetProfile, User user) { + ActionType actionType = ActionType.DELETED; AssetProfileId assetProfileId = assetProfile.getId(); TenantId tenantId = assetProfile.getTenantId(); try { assetProfileService.deleteAssetProfile(tenantId, assetProfileId); tbClusterService.broadcastEntityStateChangeEvent(tenantId, assetProfileId, ComponentLifecycleEvent.DELETED); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, assetProfileId, assetProfile, - user, ActionType.DELETED, true, null, assetProfileId.toString()); + notificationEntityService.logEntityAction(tenantId, assetProfileId, assetProfile, null, + actionType, user, assetProfileId.toString()); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET_PROFILE), ActionType.DELETED, + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET_PROFILE), actionType, user, e, assetProfileId.toString()); throw e; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java index b71cfd222e..96611bb3b5 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java @@ -22,13 +22,10 @@ import org.thingsboard.server.common.data.EntityType; 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.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import java.util.List; - @Service @AllArgsConstructor public class DefaultTbCustomerService extends AbstractTbEntityService implements TbCustomerService { @@ -40,7 +37,7 @@ public class DefaultTbCustomerService extends AbstractTbEntityService implements try { Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer)); autoCommit(user, savedCustomer.getId()); - notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedCustomer.getId(), savedCustomer, null, actionType, user); + notificationEntityService.logEntityAction(tenantId, savedCustomer.getId(), savedCustomer, null, actionType, user); return savedCustomer; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.CUSTOMER), customer, actionType, user, e); @@ -50,17 +47,17 @@ public class DefaultTbCustomerService extends AbstractTbEntityService implements @Override public void delete(Customer customer, User user) { + ActionType actionType = ActionType.DELETED; TenantId tenantId = customer.getTenantId(); CustomerId customerId = customer.getId(); try { - List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, customer.getId()); customerService.deleteCustomer(tenantId, customerId); - notificationEntityService.notifyDeleteEntity(tenantId, customer.getId(), customer, customerId, - ActionType.DELETED, relatedEdgeIds, user, customerId.toString()); + notificationEntityService.logEntityAction(tenantId, customer.getId(), customer, customerId, actionType, + user, customerId.toString()); tbClusterService.broadcastEntityStateChangeEvent(tenantId, customer.getId(), ComponentLifecycleEvent.DELETED); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.CUSTOMER), ActionType.DELETED, - user, e, customerId.toString()); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.CUSTOMER), actionType, user, + e, customerId.toString()); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java index fa5b967bd0..70918486a3 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java @@ -34,7 +34,6 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import java.util.HashSet; -import java.util.List; import java.util.Set; @Service @@ -51,8 +50,8 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement try { Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard)); autoCommit(user, savedDashboard.getId()); - notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedDashboard.getId(), savedDashboard, - null, actionType, user); + notificationEntityService.logEntityAction(tenantId, savedDashboard.getId(), savedDashboard, null, + actionType, user); return savedDashboard; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), dashboard, actionType, user, e); @@ -62,15 +61,14 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement @Override public void delete(Dashboard dashboard, User user) { + ActionType actionType = ActionType.DELETED; DashboardId dashboardId = dashboard.getId(); TenantId tenantId = dashboard.getTenantId(); try { - List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, dashboardId); dashboardService.deleteDashboard(tenantId, dashboardId); - notificationEntityService.notifyDeleteEntity(tenantId, dashboardId, dashboard, null, - ActionType.DELETED, relatedEdgeIds, user, dashboardId.toString()); + notificationEntityService.logEntityAction(tenantId, dashboardId, dashboard, null, actionType, user, dashboardId.toString()); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), ActionType.DELETED, user, e, dashboardId.toString()); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); throw e; } } @@ -83,8 +81,8 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement DashboardId dashboardId = dashboard.getId(); try { Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, customerId)); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customerId, savedDashboard, - actionType, user, dashboardId.toString(), customerId.toString(), customer.getName()); + notificationEntityService.logEntityAction(tenantId, dashboardId, savedDashboard, customerId, actionType, + user, dashboardId.toString(), customerId.toString(), customer.getName()); return savedDashboard; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, @@ -101,9 +99,8 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement try { Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, publicCustomer.getId())); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, publicCustomer.getId(), savedDashboard, - actionType, user, dashboardId.toString(), - publicCustomer.getId().toString(), publicCustomer.getName()); + notificationEntityService.logEntityAction(tenantId, dashboardId, savedDashboard, publicCustomer.getId(), + actionType, user, dashboardId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedDashboard; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); @@ -119,9 +116,8 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement try { Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, publicCustomer.getId())); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, publicCustomer.getId(), dashboard, - actionType, user, dashboardId.toString(), - publicCustomer.getId().toString(), publicCustomer.getName()); + notificationEntityService.logEntityAction(tenantId, dashboardId, dashboard, publicCustomer.getId(), actionType, + user, dashboardId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedDashboard; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); @@ -159,15 +155,15 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement for (CustomerId customerId : addedCustomerIds) { savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, customerId)); ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, savedDashboard.getId(), customerId, savedDashboard, + notificationEntityService.logEntityAction(tenantId, savedDashboard.getId(), savedDashboard, customerId, actionType, user, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; for (CustomerId customerId : removedCustomerIds) { ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId); savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, customerId)); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, savedDashboard.getId(), customerId, savedDashboard, - ActionType.UNASSIGNED_FROM_CUSTOMER, user, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); + notificationEntityService.logEntityAction(tenantId, savedDashboard.getId(), savedDashboard, customerId, + actionType, user, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } return savedDashboard; } @@ -196,7 +192,7 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement for (CustomerId customerId : addedCustomerIds) { savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, customerId)); ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customerId, savedDashboard, + notificationEntityService.logEntityAction(tenantId, dashboardId, savedDashboard, customerId, actionType, user, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } return savedDashboard; @@ -226,7 +222,7 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement for (CustomerId customerId : removedCustomerIds) { ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId); savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, customerId)); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customerId, savedDashboard, + notificationEntityService.logEntityAction(tenantId, dashboardId, savedDashboard, customerId, actionType, user, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } return savedDashboard; @@ -243,9 +239,8 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement EdgeId edgeId = edge.getId(); try { Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToEdge(tenantId, dashboardId, edgeId)); - notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, dashboardId, null, - edgeId, savedDashboard, actionType, user, dashboardId.toString(), - edgeId.toString(), edge.getName()); + notificationEntityService.logEntityAction(tenantId, dashboardId, savedDashboard, null, actionType, + user, dashboardId.toString(), edgeId.toString(), edge.getName()); return savedDashboard; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), @@ -262,10 +257,8 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement EdgeId edgeId = edge.getId(); try { Dashboard savedDevice = checkNotNull(dashboardService.unassignDashboardFromEdge(tenantId, dashboardId, edgeId)); - - notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, dashboardId, null, - edgeId, dashboard, actionType, user, dashboardId.toString(), - edgeId.toString(), edge.getName()); + notificationEntityService.logEntityAction(tenantId, dashboardId, dashboard, null, actionType, + user, dashboardId.toString(), edgeId.toString(), edge.getName()); return savedDevice; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, @@ -281,7 +274,7 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement DashboardId dashboardId = dashboard.getId(); try { Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, customer.getId())); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customer.getId(), savedDashboard, + notificationEntityService.logEntityAction(tenantId, dashboardId, savedDashboard, customer.getId(), actionType, user, dashboardId.toString(), customer.getId().toString(), customer.getName()); return savedDashboard; } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java index 2ab8de9438..544a199498 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java @@ -44,8 +44,6 @@ import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import java.util.List; - @AllArgsConstructor @TbCoreComponent @Service @@ -97,10 +95,9 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); try { - List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, deviceId); deviceService.deleteDevice(tenantId, deviceId); notificationEntityService.notifyDeleteDevice(tenantId, deviceId, device.getCustomerId(), device, - relatedEdgeIds, user, deviceId.toString()); + user, deviceId.toString()); return removeAlarmsByEntityId(tenantId, deviceId); } catch (Exception e) { @@ -116,8 +113,8 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T CustomerId customerId = customer.getId(); try { Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(tenantId, deviceId, customerId)); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, deviceId, customerId, savedDevice, - actionType, user, deviceId.toString(), customerId.toString(), customer.getName()); + notificationEntityService.logEntityAction(tenantId, deviceId, savedDevice, customerId, actionType, user, + deviceId.toString(), customerId.toString(), customer.getName()); return savedDevice; } catch (Exception e) { @@ -136,8 +133,8 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T Device savedDevice = checkNotNull(deviceService.unassignDeviceFromCustomer(tenantId, deviceId)); CustomerId customerId = customer.getId(); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, deviceId, customerId, savedDevice, - actionType, user, deviceId.toString(), customerId.toString(), customer.getName()); + notificationEntityService.logEntityAction(tenantId, deviceId, savedDevice, customerId, actionType, user, + deviceId.toString(), customerId.toString(), customer.getName()); return savedDevice; } catch (Exception e) { @@ -154,9 +151,8 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T try { Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(tenantId, deviceId, publicCustomer.getId())); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, deviceId, savedDevice.getCustomerId(), savedDevice, - actionType, user, deviceId.toString(), - publicCustomer.getId().toString(), publicCustomer.getName()); + notificationEntityService.logEntityAction(tenantId, deviceId, savedDevice, savedDevice.getCustomerId(), + actionType, user, deviceId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedDevice; } catch (Exception e) { @@ -252,30 +248,32 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T EdgeId edgeId = edge.getId(); try { Device savedDevice = checkNotNull(deviceService.assignDeviceToEdge(tenantId, deviceId, edgeId)); - notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, deviceId, savedDevice.getCustomerId(), - edgeId, savedDevice, actionType, user, deviceId.toString(), edgeId.toString(), edge.getName()); + notificationEntityService.logEntityAction(tenantId, deviceId, savedDevice, savedDevice.getCustomerId(), + actionType, user, deviceId.toString(), edgeId.toString(), edge.getName()); + return savedDevice; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), - ActionType.ASSIGNED_TO_EDGE, user, e, deviceId.toString(), edgeId.toString()); + actionType, user, e, deviceId.toString(), edgeId.toString()); throw e; } } @Override public Device unassignDeviceFromEdge(Device device, Edge edge, User user) throws ThingsboardException { + ActionType actionType = ActionType.UNASSIGNED_FROM_EDGE; TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); EdgeId edgeId = edge.getId(); try { Device savedDevice = checkNotNull(deviceService.unassignDeviceFromEdge(tenantId, deviceId, edgeId)); + notificationEntityService.logEntityAction(tenantId, deviceId, savedDevice, savedDevice.getCustomerId(), + actionType, user, deviceId.toString(), edgeId.toString(), edge.getName()); - notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, deviceId, device.getCustomerId(), - edgeId, device, ActionType.UNASSIGNED_FROM_EDGE, user, deviceId.toString(), edgeId.toString(), edge.getName()); return savedDevice; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), - ActionType.UNASSIGNED_FROM_EDGE, user, e, deviceId.toString(), edgeId.toString()); + actionType, user, e, deviceId.toString(), edgeId.toString()); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java index faa6a69983..70ee722493 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java @@ -67,8 +67,8 @@ public class DefaultTbDeviceProfileService extends AbstractTbEntityService imple otaPackageStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedDeviceProfile.getId(), - savedDeviceProfile, user, actionType, true, null); + notificationEntityService.logEntityAction(tenantId, savedDeviceProfile.getId(), savedDeviceProfile, + null, actionType, user); return savedDeviceProfile; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE_PROFILE), deviceProfile, actionType, user, e); @@ -78,6 +78,7 @@ public class DefaultTbDeviceProfileService extends AbstractTbEntityService imple @Override public void delete(DeviceProfile deviceProfile, User user) { + ActionType actionType = ActionType.DELETED; DeviceProfileId deviceProfileId = deviceProfile.getId(); TenantId tenantId = deviceProfile.getTenantId(); try { @@ -85,10 +86,10 @@ public class DefaultTbDeviceProfileService extends AbstractTbEntityService imple tbClusterService.onDeviceProfileDelete(deviceProfile, null); tbClusterService.broadcastEntityStateChangeEvent(tenantId, deviceProfileId, ComponentLifecycleEvent.DELETED); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, deviceProfileId, deviceProfile, - user, ActionType.DELETED, true, null, deviceProfileId.toString()); + notificationEntityService.logEntityAction(tenantId, deviceProfileId, deviceProfile, null, + actionType, user, deviceProfileId.toString()); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE_PROFILE), ActionType.DELETED, + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE_PROFILE), actionType, user, e, deviceProfileId.toString()); throw e; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java index c51ae3352e..adc20c21d0 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java @@ -89,8 +89,8 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE CustomerId customerId = customer.getId(); try { Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(tenantId, edgeId, customerId)); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, edgeId, customerId, savedEdge, - actionType, user, edgeId.toString(), customerId.toString(), customer.getName()); + notificationEntityService.logEntityAction(tenantId, edgeId, savedEdge, customerId, actionType, + user, edgeId.toString(), customerId.toString(), customer.getName()); return savedEdge; } catch (Exception e) { @@ -108,8 +108,8 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE CustomerId customerId = customer.getId(); try { Edge savedEdge = checkNotNull(edgeService.unassignEdgeFromCustomer(tenantId, edgeId)); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, edgeId, customerId, savedEdge, - actionType, user, edgeId.toString(), customerId.toString(), customer.getName()); + notificationEntityService.logEntityAction(tenantId, edgeId, savedEdge, customerId, actionType, + user, edgeId.toString(), customerId.toString(), customer.getName()); return savedEdge; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java index cf1733490d..904d98653e 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java @@ -40,33 +40,30 @@ public class DefaultTbEntityRelationService extends AbstractTbEntityService impl @Override public void save(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user) throws ThingsboardException { + ActionType actionType = ActionType.RELATION_ADD_OR_UPDATE; try { relationService.saveRelation(tenantId, relation); - notificationEntityService.notifyRelation(tenantId, customerId, - relation, user, ActionType.RELATION_ADD_OR_UPDATE, relation); + notificationEntityService.logEntityRelationAction(tenantId, customerId, + relation, user, actionType, null, relation); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, relation.getFrom(), null, customerId, - ActionType.RELATION_ADD_OR_UPDATE, user, e, relation); - notificationEntityService.logEntityAction(tenantId, relation.getTo(), null, customerId, - ActionType.RELATION_ADD_OR_UPDATE, user, e, relation); + notificationEntityService.logEntityRelationAction(tenantId, customerId, + relation, user, actionType, e, relation); throw e; } } @Override public void delete(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user) throws ThingsboardException { + ActionType actionType = ActionType.RELATION_DELETED; try { boolean found = relationService.deleteRelation(tenantId, relation.getFrom(), relation.getTo(), relation.getType(), relation.getTypeGroup()); if (!found) { throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND); } - notificationEntityService.notifyRelation(tenantId, customerId, - relation, user, ActionType.RELATION_DELETED, relation); + notificationEntityService.logEntityRelationAction(tenantId, customerId, relation, user, actionType, null, relation); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, relation.getFrom(), null, customerId, - ActionType.RELATION_DELETED, user, e, relation); - notificationEntityService.logEntityAction(tenantId, relation.getTo(), null, customerId, - ActionType.RELATION_DELETED, user, e, relation); + notificationEntityService.logEntityRelationAction(tenantId, customerId, + relation, user, actionType, e, relation); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java index 48e6c4b634..f587e19ef2 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java @@ -81,7 +81,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView)); this.updateEntityViewAttributes(tenantId, savedEntityView, existingEntityView, user); autoCommit(user, savedEntityView.getId()); - notificationEntityService.notifyCreateOrUpdateEntity(savedEntityView.getTenantId(), savedEntityView.getId(), savedEntityView, + notificationEntityService.logEntityAction(savedEntityView.getTenantId(), savedEntityView.getId(), savedEntityView, null, actionType, user); localCache.computeIfAbsent(savedEntityView.getTenantId(), (k) -> new ConcurrentReferenceHashMap<>()).clear(); tbClusterService.broadcastEntityStateChangeEvent(savedEntityView.getTenantId(), savedEntityView.getId(), @@ -129,10 +129,9 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen TenantId tenantId = entityView.getTenantId(); EntityViewId entityViewId = entityView.getId(); try { - List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, entityViewId); entityViewService.deleteEntityView(tenantId, entityViewId); - notificationEntityService.notifyDeleteEntity(tenantId, entityViewId, entityView, entityView.getCustomerId(), ActionType.DELETED, - relatedEdgeIds, user, entityViewId.toString()); + notificationEntityService.logEntityAction(tenantId, entityViewId, entityView, entityView.getCustomerId(), + ActionType.DELETED, user, entityViewId.toString()); localCache.computeIfAbsent(tenantId, (k) -> new ConcurrentReferenceHashMap<>()).clear(); tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityViewId, ComponentLifecycleEvent.DELETED); @@ -145,15 +144,31 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen @Override public EntityView assignEntityViewToCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, User user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; CustomerId customerId = customer.getId(); try { EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(tenantId, entityViewId, customerId)); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, entityViewId, customerId, savedEntityView, - ActionType.ASSIGNED_TO_CUSTOMER, user, entityViewId.toString(), customerId.toString(), customer.getName()); + notificationEntityService.logEntityAction(tenantId, entityViewId, savedEntityView, savedEntityView.getCustomerId(), + actionType, user, entityViewId.toString(), customerId.toString(), customer.getName()); + return savedEntityView; + } catch (Exception e) { + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), + actionType, user, e, entityViewId.toString(), customerId.toString()); + throw e; + } + } + + @Override + public EntityView unassignEntityViewFromCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, User user) throws ThingsboardException { + ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; + try { + EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(tenantId, entityViewId)); + notificationEntityService.logEntityAction(tenantId, entityViewId, savedEntityView, customer.getId(), + actionType, user, savedEntityView.getId().toString(), customer.getId().toString(), customer.getName()); return savedEntityView; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), - ActionType.ASSIGNED_TO_CUSTOMER, user, e, entityViewId.toString(), customerId.toString()); + actionType, user, e, entityViewId.toString()); throw e; } } @@ -165,9 +180,8 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen try { EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(tenantId, entityViewId, publicCustomer.getId())); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, entityViewId, savedEntityView.getCustomerId(), savedEntityView, - actionType, user, savedEntityView.getId().toString(), - publicCustomer.getId().toString(), publicCustomer.getName()); + notificationEntityService.logEntityAction(tenantId, entityViewId, savedEntityView, savedEntityView.getCustomerId(), + actionType, user, savedEntityView.getId().toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedEntityView; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), @@ -178,16 +192,16 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen @Override public EntityView assignEntityViewToEdge(TenantId tenantId, CustomerId customerId, EntityViewId entityViewId, Edge edge, User user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_EDGE; EdgeId edgeId = edge.getId(); try { EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToEdge(tenantId, entityViewId, edgeId)); - notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, entityViewId, customerId, - edgeId, savedEntityView, ActionType.ASSIGNED_TO_EDGE, user, savedEntityView.getEntityId().toString(), - edgeId.toString(), edge.getName()); + notificationEntityService.logEntityAction(tenantId, entityViewId, savedEntityView, customerId, actionType, + user, savedEntityView.getEntityId().toString(), edgeId.toString(), edge.getName()); return savedEntityView; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), - ActionType.ASSIGNED_TO_EDGE, user, e, entityViewId.toString(), edgeId.toString()); + actionType, user, e, entityViewId.toString(), edgeId.toString()); throw e; } } @@ -195,34 +209,17 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen @Override public EntityView unassignEntityViewFromEdge(TenantId tenantId, CustomerId customerId, EntityView entityView, Edge edge, User user) throws ThingsboardException { + ActionType actionType = ActionType.UNASSIGNED_FROM_EDGE; EntityViewId entityViewId = entityView.getId(); EdgeId edgeId = edge.getId(); try { EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromEdge(tenantId, entityViewId, edgeId)); - notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, entityViewId, customerId, - edgeId, entityView, ActionType.UNASSIGNED_FROM_EDGE, user, entityViewId.toString(), - edgeId.toString(), edge.getName()); + notificationEntityService.logEntityAction(tenantId, entityViewId, savedEntityView, customerId, actionType, + user, entityViewId.toString(), edgeId.toString(), edge.getName()); return savedEntityView; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), - ActionType.UNASSIGNED_FROM_EDGE, user, e, entityViewId.toString(), edgeId.toString()); - throw e; - } - } - - @Override - public EntityView unassignEntityViewFromCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, User user) throws ThingsboardException { - ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; - try { - EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(tenantId, entityViewId)); - - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, entityViewId, customer.getId(), savedEntityView, - actionType, user, savedEntityView.getId().toString(), customer.getId().toString(), customer.getName()); - - return savedEntityView; - } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), - actionType, user, e, entityViewId.toString()); + actionType, user, e, entityViewId.toString(), edgeId.toString()); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java index 4d3c61e615..3cc444caf5 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java @@ -50,9 +50,8 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen try { OtaPackageInfo savedOtaPackageInfo = otaPackageService.saveOtaPackageInfo(new OtaPackageInfo(saveOtaPackageInfoRequest), saveOtaPackageInfoRequest.isUsesUrl()); - boolean sendMsgToEdge = savedOtaPackageInfo.hasUrl() || savedOtaPackageInfo.isHasData(); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedOtaPackageInfo.getId(), - savedOtaPackageInfo, user, actionType, sendMsgToEdge, null); + notificationEntityService.logEntityAction(tenantId, savedOtaPackageInfo.getId(), savedOtaPackageInfo, + null, actionType, user); return savedOtaPackageInfo; } catch (Exception e) { @@ -65,6 +64,7 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen @Override public OtaPackageInfo saveOtaPackageData(OtaPackageInfo otaPackageInfo, String checksum, ChecksumAlgorithm checksumAlgorithm, byte[] data, String filename, String contentType, User user) throws ThingsboardException { + ActionType actionType = ActionType.UPDATED; TenantId tenantId = otaPackageInfo.getTenantId(); OtaPackageId otaPackageId = otaPackageInfo.getId(); try { @@ -87,27 +87,26 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen otaPackage.setData(ByteBuffer.wrap(data)); otaPackage.setDataSize((long) data.length); OtaPackageInfo savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedOtaPackage.getId(), - savedOtaPackage, user, ActionType.UPDATED, true, null); + notificationEntityService.logEntityAction(tenantId, savedOtaPackage.getId(), savedOtaPackage, null, actionType, user); return savedOtaPackage; } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), ActionType.UPDATED, - user, e, otaPackageId.toString()); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), actionType, user, e, otaPackageId.toString()); throw e; } } @Override public void delete(OtaPackageInfo otaPackageInfo, User user) throws ThingsboardException { + ActionType actionType = ActionType.DELETED; TenantId tenantId = otaPackageInfo.getTenantId(); OtaPackageId otaPackageId = otaPackageInfo.getId(); try { otaPackageService.deleteOtaPackage(tenantId, otaPackageId); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, otaPackageId, otaPackageInfo, - user, ActionType.DELETED, true, null, otaPackageInfo.getId().toString()); + notificationEntityService.logEntityAction(tenantId, otaPackageId, otaPackageInfo, null, + actionType, user, otaPackageInfo.getId().toString()); } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), - ActionType.DELETED, user, e, otaPackageId.toString()); + actionType, user, e, otaPackageId.toString()); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java index 63e11aeb7a..40d294d238 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java @@ -20,7 +20,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.queue.Queue; @@ -71,8 +70,6 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb onQueueUpdated(savedQueue, oldQueue); } - notificationEntityService.notifySendMsgToEdgeService(queue.getTenantId(), savedQueue.getId(), create ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); - return savedQueue; } @@ -145,8 +142,6 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb } } }, DELETE_DELAY, TimeUnit.SECONDS); - - notificationEntityService.notifySendMsgToEdgeService(queue.getTenantId(), queue.getId(), EdgeEventActionType.DELETED); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java index d9f11dacb5..58bd40d070 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java @@ -54,7 +54,7 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse ActionType actionType = tbUser.getId() == null ? ActionType.ADDED : ActionType.UPDATED; try { boolean sendEmail = tbUser.getId() == null && sendActivationMail; - User savedUser = checkNotNull(userService.saveUser(tbUser)); + User savedUser = checkNotNull(userService.saveUser(tenantId, tbUser)); if (sendEmail) { UserCredentials userCredentials = userService.findUserCredentialsByUserId(tenantId, savedUser.getId()); String baseUrl = systemSecurityService.getBaseUrl(tenantId, customerId, request); @@ -68,8 +68,7 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse throw e; } } - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, customerId, savedUser.getId(), - savedUser, user, actionType, true, null); + notificationEntityService.logEntityAction(tenantId, savedUser.getId(), savedUser, customerId, actionType, user); return savedUser; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.USER), tbUser, actionType, user, e); @@ -79,16 +78,16 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse @Override public void delete(TenantId tenantId, CustomerId customerId, User tbUser, User user) throws ThingsboardException { + ActionType actionType = ActionType.DELETED; UserId userId = tbUser.getId(); try { tbAlarmService.unassignUserAlarms(tbUser.getTenantId(), tbUser, System.currentTimeMillis()); userService.deleteUser(tenantId, userId); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, customerId, userId, tbUser, - user, ActionType.DELETED, true, null, customerId.toString()); + notificationEntityService.logEntityAction(tenantId, userId, tbUser, customerId, actionType, user, customerId.toString()); } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.USER), - ActionType.DELETED, user, e, userId.toString()); + actionType, user, e, userId.toString()); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java index 7cc150b746..efc20cd422 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java @@ -40,8 +40,8 @@ public class DefaultWidgetsBundleService extends AbstractTbEntityService impleme try { WidgetsBundle savedWidgetsBundle = checkNotNull(widgetsBundleService.saveWidgetsBundle(widgetsBundle)); autoCommit(user, savedWidgetsBundle.getId()); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedWidgetsBundle.getId(), - savedWidgetsBundle, user, actionType, true, null); + notificationEntityService.logEntityAction(tenantId, savedWidgetsBundle.getId(), savedWidgetsBundle, + null, actionType, user); return savedWidgetsBundle; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.WIDGETS_BUNDLE), widgetsBundle, actionType, user, e); @@ -51,14 +51,13 @@ public class DefaultWidgetsBundleService extends AbstractTbEntityService impleme @Override public void delete(WidgetsBundle widgetsBundle, User user) { + ActionType actionType = ActionType.DELETED; TenantId tenantId = widgetsBundle.getTenantId(); try { widgetsBundleService.deleteWidgetsBundle(widgetsBundle.getTenantId(), widgetsBundle.getId()); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, widgetsBundle.getId(), widgetsBundle, - user, ActionType.DELETED, true, null); + notificationEntityService.logEntityAction(tenantId, widgetsBundle.getId(), widgetsBundle, null, actionType, user); } catch (Exception e) { - notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.WIDGETS_BUNDLE), - ActionType.DELETED, user, e, widgetsBundle.getId()); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.WIDGETS_BUNDLE), actionType, user, e, widgetsBundle.getId()); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 1087990c4e..acadd35577 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -525,7 +525,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { user.setEmail(email); user.setTenantId(tenantId); user.setCustomerId(customerId); - user = userService.saveUser(user); + user = userService.saveUser(tenantId, user); UserCredentials userCredentials = userService.findUserCredentialsByUserId(TenantId.SYS_TENANT_ID, user.getId()); userCredentials.setPassword(passwordEncoder.encode(password)); userCredentials.setEnabled(true); diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 1c6af41360..246fa4a0fa 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -16,7 +16,6 @@ package org.thingsboard.server.service.mail; import com.fasterxml.jackson.databind.JsonNode; - import freemarker.template.Configuration; import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 63eaab23f2..53ef38d96a 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -458,11 +458,6 @@ public class DefaultTbClusterService implements TbClusterService { @Override public void onDeviceUpdated(Device device, Device old) { - onDeviceUpdated(device, old, true); - } - - @Override - public void onDeviceUpdated(Device device, Device old, boolean notifyEdge) { var created = old == null; broadcastEntityChangeToTransport(device.getTenantId(), device.getId(), device, null); if (old != null) { @@ -477,9 +472,6 @@ public class DefaultTbClusterService implements TbClusterService { broadcastEntityStateChangeEvent(device.getTenantId(), device.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); sendDeviceStateServiceEvent(device.getTenantId(), device.getId(), created, !created, false); otaPackageStateService.update(device, old); - if (!created && notifyEdge) { - sendNotificationMsgToEdge(device.getTenantId(), null, device.getId(), null, null, EdgeEventActionType.UPDATED); - } } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index dc24d6df33..fef22acf40 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -32,10 +32,11 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTrigger; import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTrigger; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; @@ -63,7 +64,6 @@ import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; -import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.DataDecodingEncodingService; diff --git a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java index 5756101ee1..884c614231 100644 --- a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java +++ b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java @@ -29,7 +29,6 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.RuleChainId; @@ -185,9 +184,7 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedRuleChain.getId(), actionType.equals(ActionType.ADDED) ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); } - boolean sendMsgToEdge = RuleChainType.EDGE.equals(savedRuleChain.getType()) && actionType.equals(ActionType.UPDATED); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedRuleChain.getId(), - savedRuleChain, user, actionType, sendMsgToEdge, null); + notificationEntityService.logEntityAction(tenantId, savedRuleChain.getId(), savedRuleChain, null, actionType, user); return savedRuleChain; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ruleChain, actionType, user, e); @@ -204,11 +201,6 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement Set referencingRuleChainIds = referencingRuleNodes.stream().map(RuleNode::getRuleChainId).collect(Collectors.toSet()); - List relatedEdgeIds = null; - if (RuleChainType.EDGE.equals(ruleChain.getType())) { - relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, ruleChainId); - } - ruleChainService.deleteRuleChainById(tenantId, ruleChainId); referencingRuleChainIds.remove(ruleChain.getId()); @@ -220,7 +212,7 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement tbClusterService.broadcastEntityStateChangeEvent(tenantId, ruleChain.getId(), ComponentLifecycleEvent.DELETED); } - notificationEntityService.notifyDeleteRuleChain(tenantId, ruleChain, relatedEdgeIds, user); + notificationEntityService.logEntityAction(tenantId, ruleChainId, ruleChain, null, ActionType.DELETED, user, ruleChainId.toString()); } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.DELETED, user, e, ruleChainId.toString()); @@ -310,14 +302,8 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement notificationEntityService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user, ruleChainMetaData); - if (RuleChainType.EDGE.equals(ruleChain.getType())) { - notificationEntityService.notifySendMsgToEdgeService(tenantId, ruleChain.getId(), EdgeEventActionType.UPDATED); - } - for (RuleChain updatedRuleChain : updatedRuleChains) { - if (RuleChainType.EDGE.equals(ruleChain.getType())) { - notificationEntityService.notifySendMsgToEdgeService(tenantId, updatedRuleChain.getId(), EdgeEventActionType.UPDATED); - } else { + if (RuleChainType.CORE.equals(ruleChain.getType())) { RuleChainMetaData updatedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, updatedRuleChain.getId())); notificationEntityService.logEntityAction(tenantId, updatedRuleChain.getId(), updatedRuleChain, ActionType.UPDATED, user, updatedRuleChainMetaData); @@ -333,34 +319,34 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement @Override public RuleChain assignRuleChainToEdge(TenantId tenantId, RuleChain ruleChain, Edge edge, User user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_EDGE; RuleChainId ruleChainId = ruleChain.getId(); EdgeId edgeId = edge.getId(); try { RuleChain savedRuleChain = checkNotNull(ruleChainService.assignRuleChainToEdge(tenantId, ruleChainId, edgeId)); - notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, ruleChainId, - null, edgeId, savedRuleChain, ActionType.ASSIGNED_TO_EDGE, + notificationEntityService.logEntityAction(tenantId, ruleChainId, savedRuleChain, null, actionType, user, ruleChainId.toString(), edgeId.toString(), edge.getName()); return savedRuleChain; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), - ActionType.ASSIGNED_TO_EDGE, user, e, ruleChainId.toString(), edgeId.toString()); + actionType, user, e, ruleChainId.toString(), edgeId.toString()); throw e; } } @Override public RuleChain unassignRuleChainFromEdge(TenantId tenantId, RuleChain ruleChain, Edge edge, User user) throws ThingsboardException { + ActionType actionType = ActionType.UNASSIGNED_FROM_EDGE; RuleChainId ruleChainId = ruleChain.getId(); EdgeId edgeId = edge.getId(); try { RuleChain savedRuleChain = checkNotNull(ruleChainService.unassignRuleChainFromEdge(tenantId, ruleChainId, edgeId, false)); - notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, ruleChainId, - null, edgeId, savedRuleChain, ActionType.UNASSIGNED_FROM_EDGE, + notificationEntityService.logEntityAction(tenantId, ruleChainId, savedRuleChain, null, actionType, user, ruleChainId.toString(), edgeId.toString(), edge.getName()); return savedRuleChain; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), - ActionType.UNASSIGNED_FROM_EDGE, user, e, ruleChainId, edgeId); + actionType, user, e, ruleChainId, edgeId); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java index 3b37b0fe26..dcb6764201 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java @@ -25,15 +25,15 @@ import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityImportResult; import org.thingsboard.server.common.data.util.ThrowingRunnable; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.TbNotificationEntityService; import org.thingsboard.server.service.sync.ie.exporting.EntityExportService; import org.thingsboard.server.service.sync.ie.exporting.impl.BaseEntityExportService; @@ -119,8 +119,8 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS relationService.saveRelations(ctx.getTenantId(), new ArrayList<>(ctx.getRelations())); for (EntityRelation relation : ctx.getRelations()) { - entityNotificationService.notifyRelation(ctx.getTenantId(), null, - relation, ctx.getUser(), ActionType.RELATION_ADD_OR_UPDATE, relation); + entityNotificationService.logEntityRelationAction(ctx.getTenantId(), null, + relation, ctx.getUser(), ActionType.RELATION_ADD_OR_UPDATE, null, relation); } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java index 7f0af64ed7..3255c418c5 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java @@ -58,8 +58,8 @@ public class AssetProfileImportService extends BaseEntityImportService { - entityNotificationService.notifyRelation(tenantId, null, - existingRelation, ctx.getUser(), ActionType.RELATION_DELETED, existingRelation); + entityNotificationService.logEntityRelationAction(tenantId, null, + existingRelation, ctx.getUser(), ActionType.RELATION_DELETED, null, existingRelation); }); } else if (Objects.equal(relation.getAdditionalInfo(), existingRelation.getAdditionalInfo())) { relationsMap.remove(relation); @@ -266,8 +266,8 @@ public abstract class BaseEntityImportService taskCache; @@ -432,12 +429,11 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont return exportableEntitiesService.findEntitiesByTenantId(ctx.getTenantId(), entityType, pageLink); }, 100, entity -> { if (ctx.getImportedEntities().get(entityType) == null || !ctx.getImportedEntities().get(entityType).contains(entity.getId())) { - List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(ctx.getTenantId(), entity.getId()); exportableEntitiesService.removeById(ctx.getTenantId(), entity.getId()); ctx.addEventCallback(() -> { - entityNotificationService.notifyDeleteEntity(ctx.getTenantId(), entity.getId(), - entity, null, ActionType.DELETED, relatedEdgeIds, ctx.getUser()); + entityNotificationService.logEntityAction(ctx.getTenantId(), entity.getId(), entity, null, + ActionType.DELETED, ctx.getUser()); }); ctx.registerDeleted(entityType); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 1603d33bfd..b2b9d992e6 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -44,15 +44,15 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.rule.trigger.AlarmTrigger; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmDataQuery; -import org.thingsboard.server.common.data.notification.rule.trigger.AlarmTrigger; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmService; -import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; diff --git a/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java b/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java index 4a07444bbb..0deb2f6bb7 100644 --- a/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java @@ -16,8 +16,6 @@ package org.thingsboard.server.utils; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.model.DDFFileParser; -import org.eclipse.leshan.core.model.DefaultDDFFileValidator; import org.eclipse.leshan.core.model.InvalidDDFFileException; import org.eclipse.leshan.core.model.ObjectModel; import org.thingsboard.server.common.data.ResourceType; diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java index d5eabdff2a..71c244890d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java @@ -74,6 +74,17 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { Mockito.reset(tbClusterService, auditLogService); } + protected void testNotifyAssignUnassignEntityAllOneTime(HasName entity, EntityId entityId, EntityId originatorId, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, ActionType actionTypeEdge, Object... additionalInfo) { + int cntTime = 1; + testNotificationMsgToEdgeServiceTime(entityId, tenantId, actionTypeEdge, cntTime); + testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); + ArgumentMatcher matcherOriginatorId = argument -> argument.equals(originatorId); + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTime); + Mockito.reset(tbClusterService, auditLogService); + } + protected void testNotifyEntityAllOneTimeRelation(EntityRelation relation, TenantId tenantId, CustomerId customerId, UserId userId, String userName, ActionType actionType, Object... additionalInfo) { @@ -115,9 +126,9 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { protected void testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(HasName entity, EntityId entityId, EntityId originatorId, TenantId tenantId, CustomerId customerId, UserId userId, String userName, - ActionType actionType, Object... additionalInfo) { + ActionType actionType, ActionType actionTypeEdge, Object... additionalInfo) { int cntTime = 1; - testNotificationMsgToEdgeServiceTime(entityId, tenantId, actionType, cntTime); + testNotificationMsgToEdgeServiceTime(entityId, tenantId, actionTypeEdge, cntTime); testLogEntityActionEntityEqClass(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); ArgumentMatcher matcherOriginatorId = argument -> argument.equals(originatorId); testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTime); @@ -163,10 +174,10 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { protected void testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(HasName entity, HasName originator, TenantId tenantId, CustomerId customerId, UserId userId, String userName, - ActionType actionType, ActionType actionTypeEdge, + ActionType actionType, int cntTime, int cntTimeEdge, int cntTimeRuleEngine, Object... additionalInfo) { EntityId originatorId = createEntityId_NULL_UUID(originator); - testSendNotificationMsgToEdgeServiceTimeEntityEqAny(tenantId, actionTypeEdge, cntTimeEdge); + testSendNotificationMsgToEdgeServiceTimeEntityEqAny(tenantId, actionType, cntTimeEdge); ArgumentMatcher matcherEntityClassEquals = argument -> argument.getClass().equals(entity.getClass()); ArgumentMatcher matcherOriginatorId = argument -> argument.getClass().equals(originatorId.getClass()); ArgumentMatcher matcherCustomerId = customerId == null ? diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 7f57fbbb3f..58367c9531 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -95,6 +95,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; @@ -188,7 +189,9 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { protected String username; protected TenantId tenantId; + protected TenantProfileId tenantProfileId; protected UserId tenantAdminUserId; + protected User tenantAdminUser; protected CustomerId tenantAdminCustomerId; protected CustomerId customerId; protected TenantId differentTenantId; @@ -269,15 +272,16 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Assert.assertNotNull(savedTenant); tenantId = savedTenant.getId(); + tenantProfileId = savedTenant.getTenantProfileId(); - User tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(tenantId); - tenantAdmin.setEmail(TENANT_ADMIN_EMAIL); + tenantAdminUser = new User(); + tenantAdminUser.setAuthority(Authority.TENANT_ADMIN); + tenantAdminUser.setTenantId(tenantId); + tenantAdminUser.setEmail(TENANT_ADMIN_EMAIL); - tenantAdmin = createUserAndLogin(tenantAdmin, TENANT_ADMIN_PASSWORD); - tenantAdminUserId = tenantAdmin.getId(); - tenantAdminCustomerId = tenantAdmin.getCustomerId(); + tenantAdminUser = createUserAndLogin(tenantAdminUser, TENANT_ADMIN_PASSWORD); + tenantAdminUserId = tenantAdminUser.getId(); + tenantAdminCustomerId = tenantAdminUser.getCustomerId(); Customer customer = new Customer(); customer.setTitle("Customer"); diff --git a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java index 6ce6e22e9a..36c5eacd36 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java @@ -158,8 +158,10 @@ public class AlarmControllerTest extends AbstractControllerTest { Assert.assertEquals(alarm.getAckTs(), updatedAlarm.getAckTs()); foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class); - testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), - tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ACK); + + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(foundAlarm, customerDevice, tenantId, + customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ACK, 1, 1, 1); + Mockito.reset(tbClusterService, auditLogService); alarm = updatedAlarm; alarm.setCleared(true); @@ -170,8 +172,10 @@ public class AlarmControllerTest extends AbstractControllerTest { Assert.assertEquals(alarm.getClearTs(), updatedAlarm.getClearTs()); foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class); - testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), - tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_CLEAR); + + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(foundAlarm, customerDevice, tenantId, + customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_CLEAR, 1, 1, 1); + Mockito.reset(tbClusterService, auditLogService); alarm = updatedAlarm; alarm.setAssigneeId(tenantAdminUserId); @@ -182,8 +186,10 @@ public class AlarmControllerTest extends AbstractControllerTest { Assert.assertEquals(alarm.getAssignTs(), updatedAlarm.getAssignTs()); foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class); - testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), - tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGNED); + + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(foundAlarm, customerDevice, tenantId, + customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGNED, 1, 1, 1); + Mockito.reset(tbClusterService, auditLogService); alarm = updatedAlarm; alarm.setAssigneeId(null); @@ -194,8 +200,11 @@ public class AlarmControllerTest extends AbstractControllerTest { Assert.assertEquals(alarm.getAssignTs(), updatedAlarm.getAssignTs()); foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class); - testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(), - tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGNED); + + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(foundAlarm, customerDevice, tenantId, + customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGNED, 1, 1, 1); + Mockito.reset(tbClusterService, auditLogService); + } @Test @@ -241,7 +250,7 @@ public class AlarmControllerTest extends AbstractControllerTest { doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk()); - testNotifyEntityOneTimeMsgToEdgeServiceNever(new Alarm(alarm), alarm.getId(), alarm.getOriginator(), + testNotifyEntityAllOneTime(new Alarm(alarm), alarm.getId(), alarm.getOriginator(), tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.DELETED); } @@ -254,7 +263,7 @@ public class AlarmControllerTest extends AbstractControllerTest { doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk()); - testNotifyEntityOneTimeMsgToEdgeServiceNever(new Alarm(alarm), alarm.getId(), alarm.getOriginator(), + testNotifyEntityAllOneTime(new Alarm(alarm), alarm.getId(), alarm.getOriginator(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED); } diff --git a/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java index 40b1707271..88a8b939b8 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java @@ -50,7 +50,6 @@ import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import static org.hamcrest.Matchers.containsString; @@ -114,8 +113,8 @@ public class AssetControllerTest extends AbstractControllerTest { Asset savedAsset = doPost("/api/asset", asset, Asset.class); - testNotifyEntityOneTimeMsgToEdgeServiceNever(savedAsset, savedAsset.getId(), savedAsset.getId(), - savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED); + testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), savedTenant.getId(), + tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED); Assert.assertNotNull(savedAsset); Assert.assertNotNull(savedAsset.getId()); @@ -130,8 +129,8 @@ public class AssetControllerTest extends AbstractControllerTest { savedAsset.setName("My new asset"); doPost("/api/asset", savedAsset, Asset.class); - testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), - savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED); + testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), savedTenant.getId(), + tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED); Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(foundAsset.getName(), savedAsset.getName()); @@ -229,7 +228,8 @@ public class AssetControllerTest extends AbstractControllerTest { @Test public void testFindAssetTypesByTenantId() throws Exception { - List assets = new ArrayList<>(); + AssetProfile assetProfile = createAssetProfile("typeB"); + assetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class); Mockito.reset(tbClusterService, auditLogService); @@ -238,24 +238,25 @@ public class AssetControllerTest extends AbstractControllerTest { Asset asset = new Asset(); asset.setName("My asset B" + i); asset.setType("typeB"); - assets.add(doPost("/api/asset", asset, Asset.class)); + asset.setAssetProfileId(assetProfile.getId()); + doPost("/api/asset", asset, Asset.class); } - testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Asset(), new Asset(), + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Asset(), new Asset(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ADDED, cntTime); + ActionType.ADDED, cntTime, cntTime, cntTime); for (int i = 0; i < 7; i++) { Asset asset = new Asset(); asset.setName("My asset C" + i); asset.setType("typeC"); - assets.add(doPost("/api/asset", asset, Asset.class)); + doPost("/api/asset", asset, Asset.class); } for (int i = 0; i < 9; i++) { Asset asset = new Asset(); asset.setName("My asset A" + i); asset.setType("typeA"); - assets.add(doPost("/api/asset", asset, Asset.class)); + doPost("/api/asset", asset, Asset.class); } List assetTypes = doGetTyped("/api/asset/types", new TypeReference>() { @@ -280,7 +281,7 @@ public class AssetControllerTest extends AbstractControllerTest { doDelete("/api/asset/" + savedAsset.getId().getId().toString()) .andExpect(status().isOk()); - testNotifyEntityOneTimeMsgToEdgeServiceNever(savedAsset, savedAsset.getId(), savedAsset.getId(), + testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.DELETED, savedAsset.getId().getId().toString()); @@ -342,7 +343,7 @@ public class AssetControllerTest extends AbstractControllerTest { Asset savedAsset = doPost("/api/asset", asset, Asset.class); Assert.assertEquals("default", savedAsset.getType()); - testNotifyEntityOneTimeMsgToEdgeServiceNever(savedAsset, savedAsset.getId(), savedAsset.getId(), + testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED); } @@ -380,9 +381,9 @@ public class AssetControllerTest extends AbstractControllerTest { + "/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(savedCustomer.getId(), assignedAsset.getCustomerId()); - testNotifyEntityAllOneTime(assignedAsset, assignedAsset.getId(), assignedAsset.getId(), + testNotifyAssignUnassignEntityAllOneTime(assignedAsset, assignedAsset.getId(), assignedAsset.getId(), savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ASSIGNED_TO_CUSTOMER, assignedAsset.getId().toString(), savedCustomer.getId().toString(), savedCustomer.getTitle()); + ActionType.ASSIGNED_TO_CUSTOMER, ActionType.UPDATED, assignedAsset.getId().toString(), savedCustomer.getId().toString(), savedCustomer.getTitle()); Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(savedCustomer.getId(), foundAsset.getCustomerId()); @@ -393,9 +394,9 @@ public class AssetControllerTest extends AbstractControllerTest { doDelete("/api/customer/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(ModelConstants.NULL_UUID, unassignedAsset.getCustomerId().getId()); - testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), + testNotifyAssignUnassignEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.UNASSIGNED_FROM_CUSTOMER, savedAsset.getId().toString(), savedCustomer.getId().toString(), savedCustomer.getTitle()); + ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UPDATED, savedAsset.getId().toString(), savedCustomer.getId().toString(), savedCustomer.getTitle()); foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(ModelConstants.NULL_UUID, foundAsset.getCustomerId().getId()); @@ -415,9 +416,10 @@ public class AssetControllerTest extends AbstractControllerTest { Customer publicCustomer = doGet("/api/customer/" + assignedAsset.getCustomerId(), Customer.class); Assert.assertTrue(publicCustomer.isPublic()); - testNotifyEntityAllOneTime(assignedAsset, assignedAsset.getId(), assignedAsset.getId(), + testNotifyAssignUnassignEntityAllOneTime(assignedAsset, assignedAsset.getId(), assignedAsset.getId(), savedTenant.getId(), publicCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ASSIGNED_TO_CUSTOMER, assignedAsset.getId().toString(), publicCustomer.getId().toString(), publicCustomer.getTitle()); + ActionType.ASSIGNED_TO_CUSTOMER, ActionType.UPDATED, assignedAsset.getId().toString(), + publicCustomer.getId().toString(), publicCustomer.getTitle()); Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(publicCustomer.getId(), foundAsset.getCustomerId()); @@ -428,9 +430,10 @@ public class AssetControllerTest extends AbstractControllerTest { doDelete("/api/customer/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(ModelConstants.NULL_UUID, unassignedAsset.getCustomerId().getId()); - testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), + testNotifyAssignUnassignEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), savedTenant.getId(), publicCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.UNASSIGNED_FROM_CUSTOMER, savedAsset.getId().toString(), publicCustomer.getId().toString(), publicCustomer.getTitle()); + ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UPDATED, savedAsset.getId().toString(), + publicCustomer.getId().toString(), publicCustomer.getTitle()); foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(ModelConstants.NULL_UUID, foundAsset.getCustomerId().getId()); @@ -513,7 +516,7 @@ public class AssetControllerTest extends AbstractControllerTest { } List loadedAssets = new ArrayList<>(); PageLink pageLink = new PageLink(23); - PageData pageData = null; + PageData pageData; do { pageData = doGetTypedWithPageLink("/api/tenant/assets?", new TypeReference>() { @@ -524,14 +527,14 @@ public class AssetControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Asset(), new Asset(), + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Asset(), new Asset(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ADDED, cntEntity); + ActionType.ADDED, cntEntity, cntEntity, cntEntity); loadedAssets.removeIf(asset -> asset.getType().equals(DefaultRuleEngineStatisticsService.TB_SERVICE_QUEUE)); - Collections.sort(assets, idComparator); - Collections.sort(loadedAssets, idComparator); + assets.sort(idComparator); + loadedAssets.sort(idComparator); Assert.assertEquals(assets, loadedAssets); } @@ -574,8 +577,8 @@ public class AssetControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(assetsTitle1, idComparator); - Collections.sort(loadedAssetsTitle1, idComparator); + assetsTitle1.sort(idComparator); + loadedAssetsTitle1.sort(idComparator); Assert.assertEquals(assetsTitle1, loadedAssetsTitle1); @@ -591,8 +594,8 @@ public class AssetControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(assetsTitle2, idComparator); - Collections.sort(loadedAssetsTitle2, idComparator); + assetsTitle2.sort(idComparator); + loadedAssetsTitle2.sort(idComparator); Assert.assertEquals(assetsTitle2, loadedAssetsTitle2); @@ -661,8 +664,8 @@ public class AssetControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(assetsType1, idComparator); - Collections.sort(loadedAssetsType1, idComparator); + assetsType1.sort(idComparator); + loadedAssetsType1.sort(idComparator); Assert.assertEquals(assetsType1, loadedAssetsType1); @@ -678,8 +681,8 @@ public class AssetControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(assetsType2, idComparator); - Collections.sort(loadedAssetsType2, idComparator); + assetsType2.sort(idComparator); + loadedAssetsType2.sort(idComparator); Assert.assertEquals(assetsType2, loadedAssetsType2); @@ -738,8 +741,8 @@ public class AssetControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(assets, idComparator); - Collections.sort(loadedAssets, idComparator); + assets.sort(idComparator); + loadedAssets.sort(idComparator); Assert.assertEquals(assets, loadedAssets); } @@ -791,8 +794,8 @@ public class AssetControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(assetsTitle1, idComparator); - Collections.sort(loadedAssetsTitle1, idComparator); + assetsTitle1.sort(idComparator); + loadedAssetsTitle1.sort(idComparator); Assert.assertEquals(assetsTitle1, loadedAssetsTitle1); @@ -808,8 +811,8 @@ public class AssetControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(assetsTitle2, idComparator); - Collections.sort(loadedAssetsTitle2, idComparator); + assetsTitle2.sort(idComparator); + loadedAssetsTitle2.sort(idComparator); Assert.assertEquals(assetsTitle2, loadedAssetsTitle2); @@ -887,8 +890,8 @@ public class AssetControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(assetsType1, idComparator); - Collections.sort(loadedAssetsType1, idComparator); + assetsType1.sort(idComparator); + loadedAssetsType1.sort(idComparator); Assert.assertEquals(assetsType1, loadedAssetsType1); @@ -904,8 +907,8 @@ public class AssetControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(assetsType2, idComparator); - Collections.sort(loadedAssetsType2, idComparator); + assetsType2.sort(idComparator); + loadedAssetsType2.sort(idComparator); Assert.assertEquals(assetsType2, loadedAssetsType2); diff --git a/application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java index c84b058cc7..38420c3e32 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java @@ -355,7 +355,7 @@ public class AssetProfileControllerTest extends AbstractControllerTest { testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new AssetProfile(), new AssetProfile(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ADDED, ActionType.ADDED, cntEntity, cntEntity, cntEntity); + ActionType.ADDED, cntEntity, cntEntity, cntEntity); Mockito.reset(tbClusterService, auditLogService); List loadedAssetProfiles = new ArrayList<>(); @@ -384,7 +384,7 @@ public class AssetProfileControllerTest extends AbstractControllerTest { testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(loadedAssetProfiles.get(0), loadedAssetProfiles.get(0), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.DELETED, ActionType.DELETED, cntEntity, cntEntity, cntEntity, loadedAssetProfiles.get(0).getId().getId().toString()); + ActionType.DELETED, cntEntity, cntEntity, cntEntity, loadedAssetProfiles.get(0).getId().getId().toString()); pageLink = new PageLink(17); pageData = doGetTypedWithPageLink("/api/assetProfiles?", diff --git a/application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java index 125e533471..f5c33debb2 100644 --- a/application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java @@ -117,8 +117,8 @@ public class CustomerControllerTest extends AbstractControllerTest { Customer savedCustomer = doPost("/api/customer", customer, Customer.class); - testNotifyEntityOneTimeMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(), savedCustomer.getId(), - savedCustomer.getTenantId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + testNotifyEntityAllOneTime(savedCustomer, savedCustomer.getId(), savedCustomer.getId(), savedCustomer.getTenantId(), + new CustomerId(CustomerId.NULL_UUID), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED); Assert.assertNotNull(savedCustomer); @@ -242,7 +242,7 @@ public class CustomerControllerTest extends AbstractControllerTest { doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) .andExpect(status().isOk()); - testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(), + testNotifyEntityBroadcastEntityStateChangeEventOneTime(savedCustomer, savedCustomer.getId(), savedCustomer.getId(), savedCustomer.getTenantId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.DELETED, savedCustomer.getId().getId().toString()); } @@ -272,7 +272,7 @@ public class CustomerControllerTest extends AbstractControllerTest { doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) .andExpect(status().isOk()); - testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedCustomer, savedCustomer.getId(), + testNotifyEntityBroadcastEntityStateChangeEventOneTime(savedCustomer, savedCustomer.getId(), savedCustomer.getId(), savedCustomer.getTenantId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.DELETED, savedCustomer.getId().getId().toString()); @@ -332,9 +332,9 @@ public class CustomerControllerTest extends AbstractControllerTest { } List customers = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); - testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Customer(), new Customer(), + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Customer(), new Customer(), tenantId, tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ADDED, cntEntity); + ActionType.ADDED, cntEntity, cntEntity, cntEntity); List loadedCustomers = new ArrayList<>(135); PageLink pageLink = new PageLink(23); diff --git a/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java index 9f8e63e3b0..6f0d6f9465 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java @@ -46,7 +46,6 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import static org.hamcrest.Matchers.containsString; @@ -154,7 +153,7 @@ public class DashboardControllerTest extends AbstractControllerTest { doDelete("/api/dashboard/" + savedDashboard.getId().getId().toString()).andExpect(status().isOk()); - testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDashboard, savedDashboard.getId(), savedDashboard.getId(), + testNotifyEntityAllOneTime(savedDashboard, savedDashboard.getId(), savedDashboard.getId(), savedDashboard.getTenantId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.DELETED, savedDashboard.getId().getId().toString()); @@ -198,7 +197,7 @@ public class DashboardControllerTest extends AbstractControllerTest { testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedDashboard, assignedDashboard.getId(), assignedDashboard.getId(), savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER, - assignedDashboard .getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); + ActionType.UPDATED, assignedDashboard.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); Assert.assertTrue(foundDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo())); @@ -210,7 +209,7 @@ public class DashboardControllerTest extends AbstractControllerTest { testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedDashboard, assignedDashboard.getId(), assignedDashboard.getId(), savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UNASSIGNED_FROM_CUSTOMER, - unassignedDashboard.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); + ActionType.UPDATED, unassignedDashboard.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); Assert.assertTrue(unassignedDashboard.getAssignedCustomers() == null || unassignedDashboard.getAssignedCustomers().isEmpty()); @@ -241,7 +240,7 @@ public class DashboardControllerTest extends AbstractControllerTest { testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedDashboard, assignedDashboard.getId(), assignedDashboard.getId(), savedTenant.getId(), publicCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER, - assignedDashboard .getId().getId().toString(), publicCustomer.getId().getId().toString(), publicCustomer.getTitle()); + ActionType.UPDATED, assignedDashboard .getId().getId().toString(), publicCustomer.getId().getId().toString(), publicCustomer.getTitle()); Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); Assert.assertTrue(foundDashboard.getAssignedCustomers().contains(publicCustomer.toShortCustomerInfo())); @@ -253,7 +252,7 @@ public class DashboardControllerTest extends AbstractControllerTest { testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedDashboard, assignedDashboard.getId(), assignedDashboard.getId(), savedTenant.getId(), publicCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UNASSIGNED_FROM_CUSTOMER, - unassignedDashboard.getId().getId().toString(), publicCustomer.getId().getId().toString(), publicCustomer.getTitle()); + ActionType.UPDATED, unassignedDashboard.getId().getId().toString(), publicCustomer.getId().getId().toString(), publicCustomer.getTitle()); Assert.assertTrue(unassignedDashboard.getAssignedCustomers() == null || unassignedDashboard.getAssignedCustomers().isEmpty()); @@ -339,9 +338,9 @@ public class DashboardControllerTest extends AbstractControllerTest { dashboards.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class))); } - testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Dashboard(), new Dashboard(), + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Dashboard(), new Dashboard(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ADDED, cntEntity); + ActionType.ADDED, cntEntity, cntEntity, cntEntity); List loadedDashboards = new ArrayList<>(); PageLink pageLink = new PageLink(24); @@ -356,8 +355,8 @@ public class DashboardControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(dashboards, idComparator); - Collections.sort(loadedDashboards, idComparator); + dashboards.sort(idComparator); + loadedDashboards.sort(idComparator); Assert.assertEquals(dashboards, loadedDashboards); } @@ -400,8 +399,8 @@ public class DashboardControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(dashboardsTitle1, idComparator); - Collections.sort(loadedDashboardsTitle1, idComparator); + dashboardsTitle1.sort(idComparator); + loadedDashboardsTitle1.sort(idComparator); Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1); @@ -417,8 +416,8 @@ public class DashboardControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(dashboardsTitle2, idComparator); - Collections.sort(loadedDashboardsTitle2, idComparator); + dashboardsTitle2.sort(idComparator); + loadedDashboardsTitle2.sort(idComparator); Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2); @@ -429,9 +428,9 @@ public class DashboardControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - testNotifyManyEntityManyTimeMsgToEdgeServiceNeverAdditionalInfoAny(new Dashboard(), new Dashboard(), + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(new Dashboard(), new Dashboard(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.DELETED, cntEntity, 1); + ActionType.DELETED, ActionType.DELETED, cntEntity, cntEntity, 1); pageLink = new PageLink(4, 0, title1); pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", @@ -474,7 +473,7 @@ public class DashboardControllerTest extends AbstractControllerTest { testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Dashboard(), new Dashboard(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ADDED, ActionType.ASSIGNED_TO_CUSTOMER, cntEntity, cntEntity, cntEntity*2); + ActionType.ADDED, cntEntity, cntEntity, cntEntity*2); List loadedDashboards = new ArrayList<>(); PageLink pageLink = new PageLink(21); @@ -489,8 +488,8 @@ public class DashboardControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(dashboards, idComparator); - Collections.sort(loadedDashboards, idComparator); + dashboards.sort(idComparator); + loadedDashboards.sort(idComparator); Assert.assertEquals(dashboards, loadedDashboards); } diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 1c952bd549..5cc9c3dc91 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -156,9 +156,8 @@ public class DeviceControllerTest extends AbstractControllerTest { Device oldDevice = new Device(savedDevice); - testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDevice, savedDevice.getId(), savedDevice.getId(), - savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ADDED); + testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(), + tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED); testNotificationUpdateGatewayNever(); Assert.assertNotNull(savedDevice); @@ -212,7 +211,7 @@ public class DeviceControllerTest extends AbstractControllerTest { Device oldDevice = new Device(savedDevice); - testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDevice, savedDevice.getId(), savedDevice.getId(), + testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED); testNotificationUpdateGatewayNever(); @@ -436,6 +435,9 @@ public class DeviceControllerTest extends AbstractControllerTest { @Test public void testFindDeviceTypesByTenantId() throws Exception { + DeviceProfile deviceProfile = createDeviceProfile("typeB"); + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + List devices = new ArrayList<>(); int cntEntity = 3; @@ -446,12 +448,13 @@ public class DeviceControllerTest extends AbstractControllerTest { Device device = new Device(); device.setName("My device B" + i); device.setType("typeB"); + device.setDeviceProfileId(deviceProfile.getId()); devices.add(doPost("/api/device", device, Device.class)); } - testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Device(), new Device(), + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Device(), new Device(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ADDED, cntEntity); + ActionType.ADDED, cntEntity, cntEntity, cntEntity); testNotificationUpdateGatewayNever(); for (int i = 0; i < 7; i++) { @@ -491,7 +494,7 @@ public class DeviceControllerTest extends AbstractControllerTest { doDelete("/api/device/" + savedDevice.getId().getId()) .andExpect(status().isOk()); - testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(), + testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.DELETED, savedDevice.getId().getId().toString()); testNotificationDeleteGatewayOneTime(savedDevice); @@ -511,7 +514,7 @@ public class DeviceControllerTest extends AbstractControllerTest { Device savedDevice = doPost("/api/device", device, Device.class); Assert.assertEquals("default", savedDevice.getType()); - testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDevice, savedDevice.getId(), savedDevice.getId(), + testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED); testNotificationUpdateGatewayNever(); @@ -551,9 +554,9 @@ public class DeviceControllerTest extends AbstractControllerTest { + "/device/" + savedDevice.getId().getId(), Device.class); Assert.assertEquals(savedCustomer.getId(), assignedDevice.getCustomerId()); - testNotifyEntityAllOneTime(assignedDevice, assignedDevice.getId(), assignedDevice.getId(), savedTenant.getId(), + testNotifyAssignUnassignEntityAllOneTime(assignedDevice, assignedDevice.getId(), assignedDevice.getId(), savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER, - assignedDevice.getId().getId().toString(), savedCustomer.getId().getId().toString(), + ActionType.UPDATED, assignedDevice.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); testNotificationUpdateGatewayNever(); @@ -566,9 +569,9 @@ public class DeviceControllerTest extends AbstractControllerTest { doDelete("/api/customer/device/" + savedDevice.getId().getId(), Device.class); Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDevice.getCustomerId().getId()); - testNotifyEntityAllOneTime(unassignedDevice, unassignedDevice.getId(), unassignedDevice.getId(), savedTenant.getId(), + testNotifyAssignUnassignEntityAllOneTime(unassignedDevice, unassignedDevice.getId(), unassignedDevice.getId(), savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UNASSIGNED_FROM_CUSTOMER, - unassignedDevice.getId().getId().toString(), savedCustomer.getId().getId().toString(), + ActionType.UPDATED, unassignedDevice.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); testNotificationDeleteGatewayNever(); @@ -590,9 +593,9 @@ public class DeviceControllerTest extends AbstractControllerTest { Customer publicCustomer = doGet("/api/customer/" + assignedDevice.getCustomerId(), Customer.class); Assert.assertTrue(publicCustomer.isPublic()); - testNotifyEntityAllOneTime(assignedDevice, assignedDevice.getId(), assignedDevice.getId(), savedTenant.getId(), + testNotifyAssignUnassignEntityAllOneTime(assignedDevice, assignedDevice.getId(), assignedDevice.getId(), savedTenant.getId(), publicCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER, - assignedDevice.getId().getId().toString(), publicCustomer.getId().getId().toString(), + ActionType.UPDATED, assignedDevice.getId().getId().toString(), publicCustomer.getId().getId().toString(), publicCustomer.getTitle()); testNotificationUpdateGatewayNever(); @@ -605,9 +608,9 @@ public class DeviceControllerTest extends AbstractControllerTest { doDelete("/api/customer/device/" + savedDevice.getId().getId(), Device.class); Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDevice.getCustomerId().getId()); - testNotifyEntityAllOneTime(unassignedDevice, unassignedDevice.getId(), unassignedDevice.getId(), savedTenant.getId(), + testNotifyAssignUnassignEntityAllOneTime(unassignedDevice, unassignedDevice.getId(), unassignedDevice.getId(), savedTenant.getId(), publicCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UNASSIGNED_FROM_CUSTOMER, - unassignedDevice.getId().getId().toString(), publicCustomer.getId().getId().toString(), + ActionType.UPDATED, unassignedDevice.getId().getId().toString(), publicCustomer.getId().getId().toString(), publicCustomer.getTitle()); testNotificationDeleteGatewayNever(); @@ -842,9 +845,9 @@ public class DeviceControllerTest extends AbstractControllerTest { List devices = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); - testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Device(), new Device(), + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Device(), new Device(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ADDED, cntEntity); + ActionType.ADDED, cntEntity, cntEntity, cntEntity); testNotificationUpdateGatewayNever(); List loadedDevices = new ArrayList<>(cntEntity); @@ -865,9 +868,9 @@ public class DeviceControllerTest extends AbstractControllerTest { deleteEntitiesAsync("/api/device/", loadedDevices, executor).get(TIMEOUT, TimeUnit.SECONDS); - testNotifyManyEntityManyTimeMsgToEdgeServiceNeverAdditionalInfoAny(new Device(), new Device(), + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(new Device(), new Device(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.DELETED, cntEntity, 1); + ActionType.DELETED, ActionType.DELETED, cntEntity, cntEntity,1); testNotificationUpdateGatewayNever(); } @@ -1052,7 +1055,7 @@ public class DeviceControllerTest extends AbstractControllerTest { testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Device(), new Device(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ADDED, ActionType.ASSIGNED_TO_CUSTOMER, cntEntity, cntEntity, cntEntity * 2); + ActionType.ADDED, cntEntity, cntEntity, cntEntity * 2); Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); testNotificationUpdateGatewayNever(); Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); @@ -1074,7 +1077,7 @@ public class DeviceControllerTest extends AbstractControllerTest { testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(new Device(), new Device(), savedTenant.getId(), customerId, tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UNASSIGNED_FROM_CUSTOMER, cntEntity, cntEntity, 3); + ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UPDATED, cntEntity, cntEntity, 3); testNotificationUpdateGatewayNever(); } diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java index d2bcfe2b88..14b052d93c 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java @@ -498,7 +498,7 @@ public class DeviceProfileControllerTest extends AbstractControllerTest { testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new DeviceProfile(), new DeviceProfile(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ADDED, ActionType.ADDED, cntEntity, cntEntity, cntEntity); + ActionType.ADDED, cntEntity, cntEntity, cntEntity); Mockito.reset(tbClusterService, auditLogService); List loadedDeviceProfiles = new ArrayList<>(); @@ -527,7 +527,7 @@ public class DeviceProfileControllerTest extends AbstractControllerTest { testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(loadedDeviceProfiles.get(0), loadedDeviceProfiles.get(0), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.DELETED, ActionType.DELETED, cntEntity, cntEntity, cntEntity, loadedDeviceProfiles.get(0).getId().getId().toString()); + ActionType.DELETED, cntEntity, cntEntity, cntEntity, loadedDeviceProfiles.get(0).getId().getId().toString()); pageLink = new PageLink(17); pageData = doGetTypedWithPageLink("/api/deviceProfiles?", diff --git a/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java index c8a20a38f5..78729eecf1 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; +import com.google.protobuf.AbstractMessage; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -45,7 +46,6 @@ import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; -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.security.Authority; @@ -62,6 +62,8 @@ import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; +import org.thingsboard.server.gen.edge.v1.SyncCompletedMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; @@ -77,6 +79,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; @TestPropertySource(properties = { "edges.enabled=true", + "queue.rule-engine.stats.enabled=false" }) @ContextConfiguration(classes = {EdgeControllerTest.Config.class}) @DaoSqlTest @@ -87,10 +90,6 @@ public class EdgeControllerTest extends AbstractControllerTest { private IdComparator idComparator = new IdComparator<>(); - private Tenant savedTenant; - private TenantId tenantId; - private User tenantAdmin; - ListeningExecutorService executor; List> futures; @@ -107,35 +106,14 @@ public class EdgeControllerTest extends AbstractControllerTest { } @Before - public void beforeTest() throws Exception { + public void setupEdgeTest() throws Exception { executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); - - loginSysAdmin(); - - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant for Edge"); - savedTenant = doPost("/api/tenant", tenant, Tenant.class); - tenantId = savedTenant.getId(); - Assert.assertNotNull(savedTenant); - - tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(savedTenant.getId()); - tenantAdmin.setEmail("tenant2@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); - - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + loginTenantAdmin(); } @After - public void afterTest() throws Exception { + public void teardownEdgeTest() throws Exception { executor.shutdownNow(); - - loginSysAdmin(); - - doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) - .andExpect(status().isOk()); } @Test @@ -149,13 +127,13 @@ public class EdgeControllerTest extends AbstractControllerTest { Assert.assertNotNull(savedEdge); Assert.assertNotNull(savedEdge.getId()); Assert.assertTrue(savedEdge.getCreatedTime() > 0); - Assert.assertEquals(savedTenant.getId(), savedEdge.getTenantId()); + Assert.assertEquals(tenantId, savedEdge.getTenantId()); Assert.assertNotNull(savedEdge.getCustomerId()); Assert.assertEquals(NULL_UUID, savedEdge.getCustomerId().getId()); Assert.assertEquals(edge.getName(), savedEdge.getName()); testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedEdge, savedEdge.getId(), savedEdge.getId(), - savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + tenantId, tenantAdminUser.getCustomerId(), tenantAdminUser.getId(), tenantAdminUser.getEmail(), ActionType.ADDED); savedEdge.setName("My new edge"); @@ -165,7 +143,7 @@ public class EdgeControllerTest extends AbstractControllerTest { Assert.assertEquals(foundEdge.getName(), savedEdge.getName()); testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(foundEdge, foundEdge.getId(), foundEdge.getId(), - savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + tenantId, tenantAdminUser.getCustomerId(), tenantAdminUser.getId(), tenantAdminUser.getEmail(), ActionType.UPDATED); } @@ -180,8 +158,8 @@ public class EdgeControllerTest extends AbstractControllerTest { .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); - testNotifyEntityEqualsOneTimeServiceNeverError(edge, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + testNotifyEntityEqualsOneTimeServiceNeverError(edge, tenantId, + tenantAdminUser.getId(), tenantAdminUser.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); Mockito.reset(tbClusterService, auditLogService); msgError = msgErrorFieldLength("type"); @@ -191,8 +169,8 @@ public class EdgeControllerTest extends AbstractControllerTest { .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); - testNotifyEntityEqualsOneTimeServiceNeverError(edge, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + testNotifyEntityEqualsOneTimeServiceNeverError(edge, tenantId, + tenantAdminUser.getId(), tenantAdminUser.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); Mockito.reset(tbClusterService, auditLogService); msgError = msgErrorFieldLength("label"); @@ -202,8 +180,8 @@ public class EdgeControllerTest extends AbstractControllerTest { .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); - testNotifyEntityEqualsOneTimeServiceNeverError(edge, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + testNotifyEntityEqualsOneTimeServiceNeverError(edge, tenantId, + tenantAdminUser.getId(), tenantAdminUser.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -229,7 +207,7 @@ public class EdgeControllerTest extends AbstractControllerTest { } testNotifyManyEntityManyTimeMsgToEdgeServiceNeverAdditionalInfoAny(new Edge(), new Edge(), - savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + tenantId, tenantAdminUser.getCustomerId(), tenantAdminUser.getId(), tenantAdminUser.getEmail(), ActionType.ADDED, cntEntity, 0); for (int i = 0; i < 7; i++) { @@ -262,7 +240,7 @@ public class EdgeControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedEdge, savedEdge.getId(), savedEdge.getId(), - savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + tenantId, tenantAdminUser.getCustomerId(), tenantAdminUser.getId(), tenantAdminUser.getEmail(), ActionType.DELETED, savedEdge.getId().getId().toString()); doGet("/api/edge/" + savedEdge.getId().getId().toString()) @@ -281,8 +259,8 @@ public class EdgeControllerTest extends AbstractControllerTest { .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); - testNotifyEntityEqualsOneTimeServiceNeverError(edge, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + testNotifyEntityEqualsOneTimeServiceNeverError(edge, tenantId, + tenantAdminUser.getId(), tenantAdminUser.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -296,8 +274,8 @@ public class EdgeControllerTest extends AbstractControllerTest { .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); - testNotifyEntityEqualsOneTimeServiceNeverError(edge, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + testNotifyEntityEqualsOneTimeServiceNeverError(edge, tenantId, + tenantAdminUser.getId(), tenantAdminUser.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -316,8 +294,8 @@ public class EdgeControllerTest extends AbstractControllerTest { Assert.assertEquals(savedCustomer.getId(), assignedEdge.getCustomerId()); testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedEdge, assignedEdge.getId(), assignedEdge.getId(), - savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER, - assignedEdge.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); + tenantId, savedCustomer.getId(), tenantAdminUser.getId(), tenantAdminUser.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER, + ActionType.ASSIGNED_TO_CUSTOMER, assignedEdge.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); Edge foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class); Assert.assertEquals(savedCustomer.getId(), foundEdge.getCustomerId()); @@ -327,8 +305,8 @@ public class EdgeControllerTest extends AbstractControllerTest { Assert.assertEquals(ModelConstants.NULL_UUID, unassignedEdge.getCustomerId().getId()); testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(unassignedEdge, unassignedEdge.getId(), unassignedEdge.getId(), - savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UNASSIGNED_FROM_CUSTOMER, - unassignedEdge.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); + tenantId, savedCustomer.getId(), tenantAdminUser.getId(), tenantAdminUser.getEmail(), ActionType.UNASSIGNED_FROM_CUSTOMER, + ActionType.UNASSIGNED_FROM_CUSTOMER, unassignedEdge.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class); Assert.assertEquals(ModelConstants.NULL_UUID, foundEdge.getCustomerId().getId()); @@ -375,7 +353,7 @@ public class EdgeControllerTest extends AbstractControllerTest { customer.setTitle("Different customer"); Customer savedCustomer = doPost("/api/customer", customer, Customer.class); - login(tenantAdmin.getEmail(), "testPassword1"); + loginTenantAdmin(); Edge edge = constructEdge("My edge", "default"); Edge savedEdge = doPost("/api/edge", edge, Edge.class); @@ -625,8 +603,8 @@ public class EdgeControllerTest extends AbstractControllerTest { List edges = new ArrayList<>(Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS)); testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Edge(), new Edge(), - savedTenant.getId(), customerId, tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ASSIGNED_TO_CUSTOMER, ActionType.ASSIGNED_TO_CUSTOMER, cntEntity, cntEntity, cntEntity * 2, + tenantId, customerId, tenantAdminUser.getId(), tenantAdminUser.getEmail(), + ActionType.ASSIGNED_TO_CUSTOMER, cntEntity, cntEntity, cntEntity * 2, new String(), new String(), new String()); List loadedEdges = new ArrayList<>(); @@ -731,7 +709,7 @@ public class EdgeControllerTest extends AbstractControllerTest { cntEntity = loadedEdgesTitle1.size(); testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(new Edge(), new Edge(), - savedTenant.getId(), customerId, tenantAdmin.getId(), tenantAdmin.getEmail(), + tenantId, customerId, tenantAdminUser.getId(), tenantAdminUser.getEmail(), ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UNASSIGNED_FROM_CUSTOMER, cntEntity, cntEntity, 3); pageLink = new PageLink(4, 0, title1); @@ -857,56 +835,45 @@ public class EdgeControllerTest extends AbstractControllerTest { @Test public void testSyncEdge() throws Exception { - Edge edge = doPost("/api/edge", constructEdge("Test Sync Edge", "test"), Edge.class); + Asset asset = new Asset(); + asset.setName("Test Sync Edge Asset 1"); + asset.setType("test"); + Asset savedAsset = doPost("/api/asset", asset, Asset.class); Device device = new Device(); device.setName("Test Sync Edge Device 1"); device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); + + Edge edge = doPost("/api/edge", constructEdge("Test Sync Edge", "test"), Edge.class); + doPost("/api/edge/" + edge.getId().getId().toString() + "/device/" + savedDevice.getId().getId().toString(), Device.class); - - Asset asset = new Asset(); - asset.setName("Test Sync Edge Asset 1"); - asset.setType("test"); - Asset savedAsset = doPost("/api/asset", asset, Asset.class); doPost("/api/edge/" + edge.getId().getId().toString() + "/asset/" + savedAsset.getId().getId().toString(), Asset.class); EdgeImitator edgeImitator = new EdgeImitator(EDGE_HOST, EDGE_PORT, edge.getRoutingKey(), edge.getSecret()); edgeImitator.ignoreType(UserCredentialsUpdateMsg.class); - edgeImitator.expectMessageAmount(20); + edgeImitator.expectMessageAmount(21); edgeImitator.connect(); assertThat(edgeImitator.waitForMessages()).as("await for messages on first connect").isTrue(); - assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("one msg during sync process").hasSize(1); - List ruleChainUpdateMsgs = edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class); - assertThat(ruleChainUpdateMsgs).as("one msg during sync process, another from edge creation").hasSize(2); - assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("one msg during sync process for 'default' device profile").hasSize(3); - assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("one msg once device assigned to edge").hasSize(2); - assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("two msgs during sync process for 'default' and 'test' asset profiles").hasSize(4); - assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("two msgs - one during sync process, and one more once asset assigned to edge").hasSize(2); - assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("one msg during sync process for tenant admin user").hasSize(1); - assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update").hasSize(4); - assertThat(edgeImitator.findAllMessagesByType(CustomerUpdateMsg.class)).as("one msg during sync process for 'Public' customer").hasSize(1); - verifyRuleChainMsgsAreRoot(ruleChainUpdateMsgs); - - edgeImitator.expectMessageAmount(15); + verifyFetchersMsgs(edgeImitator); + // verify queue msgs + Assert.assertTrue(popRuleChainMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, "Edge Root Rule Chain")); + Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); + Assert.assertTrue(popDeviceMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Device 1")); + Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "test")); + Assert.assertTrue(popAssetMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Asset 1")); + Assert.assertTrue(edgeImitator.getDownlinkMsgs().isEmpty()); + + edgeImitator.expectMessageAmount(16); doPost("/api/edge/sync/" + edge.getId()); assertThat(edgeImitator.waitForMessages()).as("await for messages after edge sync rest api call").isTrue(); - assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("queue msg").hasSize(1); - ruleChainUpdateMsgs = edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class); - assertThat(ruleChainUpdateMsgs).as("rule chain msg").hasSize(1); - assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("device profile msg").hasSize(2); - assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("asset profile msg").hasSize(3); - assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("asset update msg").hasSize(1); - assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("user update msg").hasSize(1); - assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update msg").hasSize(4); - assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("asset update msg").hasSize(1); - assertThat(edgeImitator.findAllMessagesByType(CustomerUpdateMsg.class)).as("one msg during sync process for 'Public' customer").hasSize(1); - verifyRuleChainMsgsAreRoot(ruleChainUpdateMsgs); + verifyFetchersMsgs(edgeImitator); + Assert.assertTrue(edgeImitator.getDownlinkMsgs().isEmpty()); edgeImitator.allowIgnoredTypes(); try { @@ -922,23 +889,174 @@ public class EdgeControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - private void verifyRuleChainMsgsAreRoot(List ruleChainUpdateMsgs) { - for (RuleChainUpdateMsg ruleChainUpdateMsg : ruleChainUpdateMsgs) { - Assert.assertTrue(ruleChainUpdateMsg.getRoot()); + private void verifyFetchersMsgs(EdgeImitator edgeImitator) { + Assert.assertTrue(popQueueMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Main")); + Assert.assertTrue(popRuleChainMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Edge Root Rule Chain")); + Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mail", true)); + Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mail", false)); + Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mailTemplates", true)); + Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mailTemplates", false)); + Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); + Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); + Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "test")); + Assert.assertTrue(popUserMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, TENANT_ADMIN_EMAIL, Authority.TENANT_ADMIN)); + Assert.assertTrue(popCustomerMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Public")); + Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); + Assert.assertTrue(popDeviceMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Device 1")); + Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "test")); + Assert.assertTrue(popAssetMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Asset 1")); + Assert.assertTrue(popSyncCompletedMsg(edgeImitator.getDownlinkMsgs())); + } + + private boolean popQueueMsg(List messages, UpdateMsgType msgType, String name) { + for (AbstractMessage message : messages) { + if (message instanceof QueueUpdateMsg) { + QueueUpdateMsg queueUpdateMsg = (QueueUpdateMsg) message; + if (msgType.equals(queueUpdateMsg.getMsgType()) + && name.equals(queueUpdateMsg.getName())) { + messages.remove(message); + return true; + } + } + } + return false; + } + + private boolean popRuleChainMsg(List messages, UpdateMsgType msgType, String name) { + for (AbstractMessage message : messages) { + if (message instanceof RuleChainUpdateMsg) { + RuleChainUpdateMsg ruleChainUpdateMsg = (RuleChainUpdateMsg) message; + if (msgType.equals(ruleChainUpdateMsg.getMsgType()) + && name.equals(ruleChainUpdateMsg.getName()) + && ruleChainUpdateMsg.getRoot()) { + messages.remove(message); + return true; + } + } + } + return false; + } + + private boolean popAdminSettingsMsg(List messages, String key, boolean isSystem) { + for (AbstractMessage message : messages) { + if (message instanceof AdminSettingsUpdateMsg) { + AdminSettingsUpdateMsg adminSettingsUpdateMsg = (AdminSettingsUpdateMsg) message; + if (key.equals(adminSettingsUpdateMsg.getKey()) + && isSystem == adminSettingsUpdateMsg.getIsSystem()) { + messages.remove(message); + return true; + } + } + } + return false; + } + + private boolean popDeviceProfileMsg(List messages, UpdateMsgType msgType, String name) { + for (AbstractMessage message : messages) { + if (message instanceof DeviceProfileUpdateMsg) { + DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) message; + if (msgType.equals(deviceProfileUpdateMsg.getMsgType()) + && name.equals(deviceProfileUpdateMsg.getName())) { + messages.remove(message); + return true; + } + } + } + return false; + } + + private boolean popDeviceMsg(List messages, UpdateMsgType msgType, String name) { + for (AbstractMessage message : messages) { + if (message instanceof DeviceUpdateMsg) { + DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) message; + if (msgType.equals(deviceUpdateMsg.getMsgType()) + && name.equals(deviceUpdateMsg.getName())) { + messages.remove(message); + return true; + } + } + } + return false; + } + + private boolean popAssetProfileMsg(List messages, UpdateMsgType msgType, String name) { + for (AbstractMessage message : messages) { + if (message instanceof AssetProfileUpdateMsg) { + AssetProfileUpdateMsg assetProfileUpdateMsg = (AssetProfileUpdateMsg) message; + if (msgType.equals(assetProfileUpdateMsg.getMsgType()) + && name.equals(assetProfileUpdateMsg.getName())) { + messages.remove(message); + return true; + } + } + } + return false; + } + + private boolean popAssetMsg(List messages, UpdateMsgType msgType, String name) { + for (AbstractMessage message : messages) { + if (message instanceof AssetUpdateMsg) { + AssetUpdateMsg assetUpdateMsg = (AssetUpdateMsg) message; + if (msgType.equals(assetUpdateMsg.getMsgType()) + && name.equals(assetUpdateMsg.getName())) { + messages.remove(message); + return true; + } + } + } + return false; + } + + private boolean popUserMsg(List messages, UpdateMsgType msgType, String email, Authority authority) { + for (AbstractMessage message : messages) { + if (message instanceof UserUpdateMsg) { + UserUpdateMsg userUpdateMsg = (UserUpdateMsg) message; + if (msgType.equals(userUpdateMsg.getMsgType()) + && email.equals(userUpdateMsg.getEmail()) + && authority.name().equals(userUpdateMsg.getAuthority())) { + messages.remove(message); + return true; + } + } + } + return false; + } + + private boolean popCustomerMsg(List messages, UpdateMsgType msgType, String title) { + for (AbstractMessage message : messages) { + if (message instanceof CustomerUpdateMsg) { + CustomerUpdateMsg customerUpdateMsg = (CustomerUpdateMsg) message; + if (msgType.equals(customerUpdateMsg.getMsgType()) + && title.equals(customerUpdateMsg.getTitle())) { + messages.remove(message); + return true; + } + } + } + return false; + } + + private boolean popSyncCompletedMsg(List messages) { + for (AbstractMessage message : messages) { + if (message instanceof SyncCompletedMsg) { + messages.remove(message); + return true; + } } + return false; } @Test public void testDeleteEdgeWithDeleteRelationsOk() throws Exception { EdgeId edgeId = savedEdge("Edge for Test WithRelationsOk").getId(); - testEntityDaoWithRelationsOk(savedTenant.getId(), edgeId, "/api/edge/" + edgeId); + testEntityDaoWithRelationsOk(tenantId, edgeId, "/api/edge/" + edgeId); } @Ignore @Test public void testDeleteEdgeExceptionWithRelationsTransactional() throws Exception { EdgeId edgeId = savedEdge("Edge for Test WithRelations Transactional Exception").getId(); - testEntityDaoWithRelationsTransactionalException(edgeDao, savedTenant.getId(), edgeId, "/api/edge/" + edgeId); + testEntityDaoWithRelationsTransactionalException(edgeDao, tenantId, edgeId, "/api/edge/" + edgeId); } private Edge savedEdge(String name) { diff --git a/application/src/test/java/org/thingsboard/server/controller/EdgeEventControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EdgeEventControllerTest.java index 255aa2bdf1..04842df6a0 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EdgeEventControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EdgeEventControllerTest.java @@ -27,8 +27,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.test.context.TestPropertySource; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -38,7 +36,6 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.edge.EdgeEventDao; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; @@ -54,18 +51,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @TestPropertySource(properties = { "edges.enabled=true", + "queue.rule-engine.stats.enabled=false" }) @Slf4j @DaoSqlTest public class EdgeEventControllerTest extends AbstractControllerTest { - private Tenant savedTenant; - private User tenantAdmin; - @Autowired private EdgeEventDao edgeEventDao; @SpyBean @@ -80,33 +74,11 @@ public class EdgeEventControllerTest extends AbstractControllerTest { @Before public void beforeTest() throws Exception { - loginSysAdmin(); - - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - savedTenant = doPost("/api/tenant", tenant, Tenant.class); - Assert.assertNotNull(savedTenant); - - tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(savedTenant.getId()); - tenantAdmin.setEmail("tenant2@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); - - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); - // sleep 1 seconds to avoid CREDENTIALS updated message for the user - // user credentials is going to be stored and updated event pushed to edge notification service - // while service will be processing this event edge could be already added and additional message will be pushed - Thread.sleep(1000); + loginTenantAdmin(); } @After public void afterTest() throws Exception { - loginSysAdmin(); - - doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) - .andExpect(status().isOk()); } @Test @@ -127,19 +99,37 @@ public class EdgeEventControllerTest extends AbstractControllerTest { EntityRelation relation = new EntityRelation(savedAsset.getId(), savedDevice.getId(), EntityRelation.CONTAINS_TYPE); + awaitForNumberOfEdgeEvents(edgeId, 3); + doPost("/api/relation", relation); + awaitForNumberOfEdgeEvents(edgeId, 4); + + List edgeEvents = findEdgeEvents(edgeId); + Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.RULE_CHAIN)); // root rule chain + Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.DEVICE)); // TestDevice + Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.ASSET)); // TestAsset + Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.RELATION)); + Assert.assertTrue(edgeEvents.isEmpty()); + } + + private boolean popEdgeEvent(List edgeEvents, EdgeEventType edgeEventType) { + for (EdgeEvent edgeEvent : edgeEvents) { + if (edgeEventType.equals(edgeEvent.getType())) { + edgeEvents.remove(edgeEvent); + return true; + } + } + return false; + } + + private void awaitForNumberOfEdgeEvents(EdgeId edgeId, int expectedNumber) { Awaitility.await() .atMost(30, TimeUnit.SECONDS) .until(() -> { List edgeEvents = findEdgeEvents(edgeId); - return edgeEvents.size() == 4; + return edgeEvents.size() == expectedNumber; }); - List edgeEvents = findEdgeEvents(edgeId); - Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RULE_CHAIN.equals(ee.getType()))); - Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.DEVICE.equals(ee.getType()))); - Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.ASSET.equals(ee.getType()))); - Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RELATION.equals(ee.getType()))); } @Test @@ -195,7 +185,7 @@ public class EdgeEventControllerTest extends AbstractControllerTest { edgeEvent.setCreatedTime(System.currentTimeMillis()); edgeEvent.setTenantId(tenantId); edgeEvent.setAction(EdgeEventActionType.ADDED); - edgeEvent.setEntityId(tenantAdmin.getUuidId()); + edgeEvent.setEntityId(tenantAdminUser.getUuidId()); edgeEvent.setType(EdgeEventType.ALARM); try { edgeEventDao.saveAsync(edgeEvent).get(); diff --git a/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java index cb41019f6f..beabcf77d1 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java @@ -171,7 +171,7 @@ public class EntityViewControllerTest extends AbstractControllerTest { testBroadcastEntityStateChangeEventTime(foundEntityView.getId(), tenantId, 1); testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(foundEntityView, foundEntityView, tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, - ActionType.ADDED, ActionType.ADDED, 1, 0, 1); + ActionType.ADDED, 1, 1, 1); Mockito.reset(tbClusterService, auditLogService); savedView.setName("New test entity view"); @@ -184,7 +184,7 @@ public class EntityViewControllerTest extends AbstractControllerTest { testBroadcastEntityStateChangeEventTime(foundEntityView.getId(), tenantId, 1); testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(foundEntityView, foundEntityView, tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, - ActionType.UPDATED, ActionType.UPDATED, 1, 1, 5); + ActionType.UPDATED, 1, 1, 5); doGet("/api/tenant/entityViews?entityViewName=" + name) .andExpect(status().isNotFound()) @@ -248,7 +248,7 @@ public class EntityViewControllerTest extends AbstractControllerTest { doDelete("/api/entityView/" + entityIdStr) .andExpect(status().isOk()); - testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedView, savedView.getId(), savedView.getId(), + testNotifyEntityBroadcastEntityStateChangeEventOneTime(savedView, savedView.getId(), savedView.getId(), tenantId, view.getCustomerId(), tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED, entityIdStr); @@ -287,7 +287,7 @@ public class EntityViewControllerTest extends AbstractControllerTest { testBroadcastEntityStateChangeEventTime(savedView.getId(), tenantId, 1); testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(savedView, savedView, tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, - ActionType.UPDATED, ActionType.UPDATED, 1, 1, 5); + ActionType.UPDATED, 1, 1, 5); Mockito.reset(tbClusterService, auditLogService); EntityView assignedView = doPost( @@ -299,9 +299,9 @@ public class EntityViewControllerTest extends AbstractControllerTest { assertEquals(savedCustomer.getId(), foundView.getCustomerId()); testBroadcastEntityStateChangeEventNever(foundView.getId()); - testNotifyEntityAllOneTime(foundView, foundView.getId(), foundView.getId(), + testNotifyAssignUnassignEntityAllOneTime(foundView, foundView.getId(), foundView.getId(), tenantId, foundView.getCustomerId(), tenantAdminUserId, TENANT_ADMIN_EMAIL, - ActionType.ASSIGNED_TO_CUSTOMER, + ActionType.ASSIGNED_TO_CUSTOMER, ActionType.UPDATED, foundView.getId().getId().toString(), foundView.getCustomerId().getId().toString(), savedCustomer.getTitle()); EntityView unAssignedView = doDelete("/api/customer/entityView/" + savedView.getId().getId().toString(), EntityView.class); @@ -311,9 +311,9 @@ public class EntityViewControllerTest extends AbstractControllerTest { assertEquals(ModelConstants.NULL_UUID, foundView.getCustomerId().getId()); testBroadcastEntityStateChangeEventNever(foundView.getId()); - testNotifyEntityAllOneTime(unAssignedView, savedView.getId(), savedView.getId(), + testNotifyAssignUnassignEntityAllOneTime(unAssignedView, savedView.getId(), savedView.getId(), tenantId, savedView.getCustomerId(), tenantAdminUserId, TENANT_ADMIN_EMAIL, - ActionType.UNASSIGNED_FROM_CUSTOMER, + ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UPDATED, assignedView.getId().getId().toString(), savedView.getCustomerId().getId().toString(), savedCustomer.getTitle()); } @@ -329,9 +329,9 @@ public class EntityViewControllerTest extends AbstractControllerTest { Assert.assertTrue(publicCustomer.isPublic()); testBroadcastEntityStateChangeEventNever(assignedView.getId()); - testNotifyEntityAllOneTime(assignedView, assignedView.getId(), assignedView.getId(), + testNotifyAssignUnassignEntityAllOneTime(assignedView, assignedView.getId(), assignedView.getId(), tenantId, assignedView.getCustomerId(), tenantAdminUserId, TENANT_ADMIN_EMAIL, - ActionType.ASSIGNED_TO_CUSTOMER, + ActionType.ASSIGNED_TO_CUSTOMER, ActionType.UPDATED, assignedView.getId().getId().toString(), assignedView.getCustomerId().getId().toString(), publicCustomer.getTitle()); EntityView foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); @@ -344,9 +344,9 @@ public class EntityViewControllerTest extends AbstractControllerTest { assertEquals(ModelConstants.NULL_UUID, foundView.getCustomerId().getId()); testBroadcastEntityStateChangeEventNever(foundView.getId()); - testNotifyEntityAllOneTime(unAssignedView, unAssignedView.getId(), unAssignedView.getId(), + testNotifyAssignUnassignEntityAllOneTime(unAssignedView, unAssignedView.getId(), unAssignedView.getId(), tenantId, publicCustomer.getId(), tenantAdminUserId, TENANT_ADMIN_EMAIL, - ActionType.UNASSIGNED_FROM_CUSTOMER, + ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UPDATED, unAssignedView.getId().getId().toString(), publicCustomer.getId().getId().toString(), publicCustomer.getTitle()); } @@ -426,12 +426,12 @@ public class EntityViewControllerTest extends AbstractControllerTest { testNotifyEntityBroadcastEntityStateChangeEventMany(new EntityView(), new EntityView(), tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, - ActionType.ADDED, ActionType.ADDED, cntEntity, 0, cntEntity * 2, 0); + ActionType.ADDED, ActionType.ADDED, cntEntity, cntEntity, cntEntity * 2, 0); testNotifyEntityBroadcastEntityStateChangeEventMany(new EntityView(), new EntityView(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, - ActionType.ASSIGNED_TO_CUSTOMER, ActionType.ASSIGNED_TO_CUSTOMER, cntEntity, cntEntity, - cntEntity * 2, 3); + ActionType.ASSIGNED_TO_CUSTOMER, ActionType.UPDATED, cntEntity, cntEntity, + cntEntity*2, 3); } @Test @@ -465,7 +465,7 @@ public class EntityViewControllerTest extends AbstractControllerTest { testBroadcastEntityStateChangeEventNever(loadedNamesOfView1.get(0).getId()); testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(new EntityView(), new EntityView(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, - ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UNASSIGNED_FROM_CUSTOMER, cntEntity, cntEntity, 3); + ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UPDATED, cntEntity, cntEntity, 3); PageData pageData = doGetTypedWithPageLink(urlTemplate, PAGE_DATA_ENTITY_VIEW_TYPE_REF, new PageLink(4, 0, name1)); diff --git a/application/src/test/java/org/thingsboard/server/controller/OtaPackageControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/OtaPackageControllerTest.java index f4472e83be..3ebbb9ebbc 100644 --- a/application/src/test/java/org/thingsboard/server/controller/OtaPackageControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/OtaPackageControllerTest.java @@ -339,7 +339,7 @@ public class OtaPackageControllerTest extends AbstractControllerTest { testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new OtaPackageInfo(), new OtaPackageInfo(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ADDED, ActionType.ADDED, cntEntity, 0, (cntEntity*2 - startIndexSaveData)); + ActionType.ADDED, cntEntity, 0, (cntEntity*2 - startIndexSaveData)); List loadedFirmwares = new ArrayList<>(); PageLink pageLink = new PageLink(24); diff --git a/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java index 59a305fe5a..cafeabcf16 100644 --- a/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java @@ -21,7 +21,6 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import org.junit.jupiter.api.Assertions; import org.mockito.AdditionalAnswers; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; @@ -31,7 +30,6 @@ import org.springframework.test.context.ContextConfiguration; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.action.TbCreateAlarmNode; import org.thingsboard.rule.engine.action.TbCreateAlarmNodeConfiguration; -import org.thingsboard.rule.engine.api.TbVersionedNode; import org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode; import org.thingsboard.rule.engine.metadata.TbGetRelatedDataNodeConfiguration; import org.thingsboard.server.common.data.StringUtils; @@ -52,7 +50,6 @@ import org.thingsboard.server.dao.rule.RuleChainDao; import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -63,7 +60,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @DaoSqlTest public class RuleChainControllerTest extends AbstractControllerTest { - private IdComparator idComparator = new IdComparator<>(); + private final IdComparator idComparator = new IdComparator<>(); private Tenant savedTenant; private User tenantAdmin; @@ -245,7 +242,7 @@ public class RuleChainControllerTest extends AbstractControllerTest { doDelete("/api/ruleChain/" + savedRuleChain.getId().getId().toString()) .andExpect(status().isOk()); - testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedRuleChain, savedRuleChain.getId(), savedRuleChain.getId(), + testNotifyEntityBroadcastEntityStateChangeEventOneTime(savedRuleChain, savedRuleChain.getId(), savedRuleChain.getId(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.DELETED, savedRuleChain.getId().getId().toString()); @@ -260,14 +257,13 @@ public class RuleChainControllerTest extends AbstractControllerTest { Edge savedEdge = doPost("/api/edge", edge, Edge.class); - List edgeRuleChains = new ArrayList<>(); PageLink pageLink = new PageLink(17); PageData pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/ruleChains?", new TypeReference<>() { }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getTotalElements()); - edgeRuleChains.addAll(pageData.getData()); + List edgeRuleChains = new ArrayList<>(pageData.getData()); Mockito.reset(tbClusterService, auditLogService); @@ -284,11 +280,7 @@ public class RuleChainControllerTest extends AbstractControllerTest { testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new RuleChain(), new RuleChain(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ADDED, ActionType.ADDED, cntEntity, 0, cntEntity * 2); - testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new RuleChain(), new RuleChain(), - savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), - ActionType.ASSIGNED_TO_EDGE, ActionType.ASSIGNED_TO_EDGE, cntEntity, cntEntity, cntEntity * 2, - new String(), new String(), new String()); + ActionType.ADDED, cntEntity, cntEntity, cntEntity * 2); Mockito.reset(tbClusterService, auditLogService); List loadedEdgeRuleChains = new ArrayList<>(); @@ -303,8 +295,8 @@ public class RuleChainControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(edgeRuleChains, idComparator); - Collections.sort(loadedEdgeRuleChains, idComparator); + edgeRuleChains.sort(idComparator); + loadedEdgeRuleChains.sort(idComparator); Assert.assertEquals(edgeRuleChains, loadedEdgeRuleChains); diff --git a/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java index 67564ec898..28bb987257 100644 --- a/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java @@ -113,7 +113,7 @@ public class UserControllerTest extends AbstractControllerTest { testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(foundUser, foundUser, SYSTEM_TENANT, customerNUULId, null, SYS_ADMIN_EMAIL, - ActionType.ADDED, ActionType.ADDED, 1, 1, 1); + ActionType.ADDED, 1, 1, 1); Mockito.reset(tbClusterService, auditLogService); resetTokens(); @@ -152,7 +152,7 @@ public class UserControllerTest extends AbstractControllerTest { testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(foundUser, foundUser.getId(), foundUser.getId(), SYSTEM_TENANT, customerNUULId, null, SYS_ADMIN_EMAIL, - ActionType.DELETED, SYSTEM_TENANT.getId().toString()); + ActionType.DELETED, ActionType.DELETED, SYSTEM_TENANT.getId().toString()); } @Test @@ -397,7 +397,7 @@ public class UserControllerTest extends AbstractControllerTest { testManyUser.setTenantId(tenantId); testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(testManyUser, testManyUser, SYSTEM_TENANT, customerNUULId, null, SYS_ADMIN_EMAIL, - ActionType.ADDED, ActionType.ADDED, cntEntity, cntEntity, cntEntity); + ActionType.ADDED, cntEntity, cntEntity, cntEntity); List loadedTenantAdmins = new ArrayList<>(); PageLink pageLink = new PageLink(33); @@ -510,7 +510,7 @@ public class UserControllerTest extends AbstractControllerTest { testManyUser.setTenantId(tenantId); testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(testManyUser, testManyUser, SYSTEM_TENANT, customerNUULId, null, SYS_ADMIN_EMAIL, - ActionType.DELETED, ActionType.DELETED, cntEntity, NUMBER_OF_USERS, cntEntity, new String()); + ActionType.DELETED, cntEntity, NUMBER_OF_USERS, cntEntity, ""); pageLink = new PageLink(4, 0, email1); pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", diff --git a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java index 82b2ce3ec2..32f5aa109b 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java @@ -36,7 +36,6 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.asset.Asset; @@ -70,7 +69,6 @@ import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; -import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.edge.imitator.EdgeImitator; @@ -85,6 +83,7 @@ import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; +import org.thingsboard.server.gen.edge.v1.SyncCompletedMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UplinkMsg; import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; @@ -100,16 +99,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @TestPropertySource(properties = { "edges.enabled=true", - "queue.rule-engine.stats.enabled=false", + "queue.rule-engine.stats.enabled=false" }) abstract public class AbstractEdgeTest extends AbstractControllerTest { private static final String THERMOSTAT_DEVICE_PROFILE_NAME = "Thermostat"; - protected Tenant savedTenant; - protected TenantId tenantId; - protected User tenantAdmin; - protected DeviceProfile thermostatDeviceProfile; protected EdgeImitator edgeImitator; @@ -125,27 +120,8 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { protected TbClusterService clusterService; @Before - public void beforeTest() throws Exception { - loginSysAdmin(); - - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - savedTenant = doPost("/api/tenant", tenant, Tenant.class); - tenantId = savedTenant.getId(); - Assert.assertNotNull(savedTenant); - - tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(savedTenant.getId()); - tenantAdmin.setEmail("tenant2@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); - - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); - // sleep 0.5 second to avoid CREDENTIALS updated message for the user - // user credentials is going to be stored and updated event pushed to edge notification service - // while service will be processing this event edge could be already added and additional message will be pushed - Thread.sleep(500); + public void setupEdgeTest() throws Exception { + loginTenantAdmin(); installation(); @@ -181,31 +157,32 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { } @After - public void afterTest() throws Exception { + public void teardownEdgeTest() { try { - edgeImitator.disconnect(); - } catch (Exception ignored){} - - loginSysAdmin(); + edgeImitator.expectMessageAmount(2); + loginTenantAdmin(); + Assert.assertTrue(edgeImitator.waitForMessages()); - doDelete("/api/tenant/" + savedTenant.getUuidId()) - .andExpect(status().isOk()); + doDelete("/api/edge/" + edge.getId().toString()) + .andExpect(status().isOk()); + edgeImitator.disconnect(); + } catch (Exception ignored) {} } private void installation() { - edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class); - thermostatDeviceProfile = this.createDeviceProfile(THERMOSTAT_DEVICE_PROFILE_NAME, createMqttDeviceProfileTransportConfiguration(new JsonTransportPayloadConfiguration(), false)); - extendDeviceProfileData(thermostatDeviceProfile); thermostatDeviceProfile = doPost("/api/deviceProfile", thermostatDeviceProfile, DeviceProfile.class); Device savedDevice = saveDevice("Edge Device 1", THERMOSTAT_DEVICE_PROFILE_NAME); - doPost("/api/edge/" + edge.getUuidId() - + "/device/" + savedDevice.getUuidId(), Device.class); Asset savedAsset = saveAsset("Edge Asset 1"); + + edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class); + + doPost("/api/edge/" + edge.getUuidId() + + "/device/" + savedDevice.getUuidId(), Device.class); doPost("/api/edge/" + edge.getUuidId() + "/asset/" + savedAsset.getUuidId(), Asset.class); } @@ -244,18 +221,8 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { validateEdgeConfiguration(); - // 5 messages - // - 2 from device profile fetcher (default and thermostat) - // - 1 from device fetcher - // - 1 from device profile controller (thermostat) - // - 1 from device controller (thermostat) - validateDeviceProfiles(); - - // 2 messages - 1 from device fetcher and 1 from device controller - validateDevices(); - - // 2 messages - 1 from asset fetcher and 1 from asset controller - validateAssets(); + // 1 message from queue fetcher + validateQueues(); // 2 messages - 1 from rule chain fetcher and 1 from rule chain controller UUID ruleChainUUID = validateRuleChains(); @@ -266,20 +233,32 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { // 4 messages - 4 messages from fetcher - 2 from system level ('mail', 'mailTemplates') and 2 from admin level ('mail', 'mailTemplates') validateAdminSettings(); + // 4 messages + // - 2 from device profile fetcher (default and thermostat) + // - 1 from device fetcher + // - 1 from device controller (thermostat) + validateDeviceProfiles(); + // 3 messages // - 1 message from asset profile fetcher // - 1 message from asset fetcher // - 1 message from asset controller validateAssetProfiles(); - // 1 message from queue fetcher - validateQueues(); + // 2 messages - 1 from device fetcher and 1 from device controller + validateDevices(); - // 1 message from user fetcher - validateUsers(); + // 2 messages - 1 from asset fetcher and 1 from asset controller + validateAssets(); // 1 message from public customer fetcher validatePublicCustomer(); + + // 1 message from user fetcher + validateUsers(); + + // 1 message sync completed + validateSyncCompleted(); } private void validateEdgeConfiguration() throws Exception { @@ -290,12 +269,11 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { private void validateDeviceProfiles() throws Exception { List deviceProfileUpdateMsgList = edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class); - // default msg - // thermostat msg from fetcher + // default msg device profile from fetcher + // thermostat msg from device profile fetcher // thermostat msg from device fetcher - // thermostat msg from controller // thermostat msg from creation of device - Assert.assertEquals(5, deviceProfileUpdateMsgList.size()); + Assert.assertEquals(4, deviceProfileUpdateMsgList.size()); Optional thermostatProfileUpdateMsgOpt = deviceProfileUpdateMsgList.stream().filter(dfum -> THERMOSTAT_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny(); Assert.assertTrue(thermostatProfileUpdateMsgOpt.isPresent()); @@ -394,7 +372,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { } } - private void validateMailAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) throws JsonProcessingException { + private void validateMailAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) { JsonNode jsonNode = JacksonUtil.toJsonNode(adminSettingsUpdateMsg.getJsonValue()); Assert.assertNotNull(jsonNode.get("mailFrom")); Assert.assertNotNull(jsonNode.get("smtpProtocol")); @@ -403,7 +381,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { Assert.assertNotNull(jsonNode.get("timeout")); } - private void validateMailTemplatesAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) throws JsonProcessingException { + private void validateMailTemplatesAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) { JsonNode jsonNode = JacksonUtil.toJsonNode(adminSettingsUpdateMsg.getJsonValue()); Assert.assertNotNull(jsonNode.get("accountActivated")); Assert.assertNotNull(jsonNode.get("accountLockout")); @@ -449,7 +427,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { UUID userUUID = new UUID(userUpdateMsg.getIdMSB(), userUpdateMsg.getIdLSB()); User user = doGet("/api/user/" + userUUID, User.class); Assert.assertNotNull(user); - Assert.assertEquals("tenant2@thingsboard.org", userUpdateMsg.getEmail()); + Assert.assertEquals("testtenant@thingsboard.org", userUpdateMsg.getEmail()); testAutoGeneratedCodeByProtobuf(userUpdateMsg); } @@ -464,6 +442,11 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { Assert.assertTrue(customer.isPublic()); } + private void validateSyncCompleted() { + Optional syncCompletedMsgOpt = edgeImitator.findMessageByType(SyncCompletedMsg.class); + Assert.assertTrue(syncCompletedMsgOpt.isPresent()); + } + protected Device saveDeviceOnCloudAndVerifyDeliveryToEdge() throws Exception { // create device and assign to edge Device savedDevice = saveDevice(StringUtils.randomAlphanumeric(15), thermostatDeviceProfile.getName()); diff --git a/application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java index 2f5848ece6..e8639e0db7 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java @@ -82,11 +82,11 @@ public class AssetEdgeTest extends AbstractEdgeTest { Assert.assertEquals(savedAsset.getUuidId().getMostSignificantBits(), assetUpdateMsg.getIdMSB()); Assert.assertEquals(savedAsset.getUuidId().getLeastSignificantBits(), assetUpdateMsg.getIdLSB()); - // delete asset - no messages expected + // delete asset - message expected, it was sent to all edges edgeImitator.expectMessageAmount(1); doDelete("/api/asset/" + savedAsset.getUuidId()) .andExpect(status().isOk()); - Assert.assertFalse(edgeImitator.waitForMessages(1)); + Assert.assertTrue(edgeImitator.waitForMessages(1)); // create asset #2 and assign to edge edgeImitator.expectMessageAmount(2); @@ -94,7 +94,6 @@ public class AssetEdgeTest extends AbstractEdgeTest { doPost("/api/edge/" + edge.getUuidId() + "/asset/" + savedAsset.getUuidId(), Asset.class); Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); assetUpdateMsgOpt = edgeImitator.findMessageByType(AssetUpdateMsg.class); Assert.assertTrue(assetUpdateMsgOpt.isPresent()); assetUpdateMsg = assetUpdateMsgOpt.get(); diff --git a/application/src/test/java/org/thingsboard/server/edge/CustomerEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/CustomerEdgeTest.java index b9bf41fde6..251b4b4caf 100644 --- a/application/src/test/java/org/thingsboard/server/edge/CustomerEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/CustomerEdgeTest.java @@ -20,12 +20,15 @@ import org.junit.Assert; import org.junit.Test; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import java.util.Optional; +import java.util.UUID; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -74,13 +77,19 @@ public class CustomerEdgeTest extends AbstractEdgeTest { Assert.assertEquals(savedCustomer.getTitle(), customerUpdateMsg.getTitle()); // delete customer - edgeImitator.expectMessageAmount(1); + edgeImitator.expectMessageAmount(2); doDelete("/api/customer/" + savedCustomer.getUuidId()) .andExpect(status().isOk()); Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg); - customerUpdateMsg = (CustomerUpdateMsg) latestMessage; + edgeConfigurationOpt = edgeImitator.findMessageByType(EdgeConfiguration.class); + Assert.assertTrue(edgeConfigurationOpt.isPresent()); + edgeConfiguration = edgeConfigurationOpt.get(); + Assert.assertEquals( + new CustomerId(EntityId.NULL_UUID), + new CustomerId(new UUID(edgeConfiguration.getCustomerIdMSB(), edgeConfiguration.getCustomerIdLSB()))); + customerUpdateOpt = edgeImitator.findMessageByType(CustomerUpdateMsg.class); + Assert.assertTrue(customerUpdateOpt.isPresent()); + customerUpdateMsg = customerUpdateOpt.get(); Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, customerUpdateMsg.getMsgType()); Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits()); Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits()); diff --git a/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java index 988d6fdc98..94743c4a6b 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DashboardEdgeTest.java @@ -77,11 +77,11 @@ public class DashboardEdgeTest extends AbstractEdgeTest { Assert.assertEquals(savedDashboard.getUuidId().getMostSignificantBits(), dashboardUpdateMsg.getIdMSB()); Assert.assertEquals(savedDashboard.getUuidId().getLeastSignificantBits(), dashboardUpdateMsg.getIdLSB()); - // delete dashboard - no messages expected + // delete dashboard - message expected, it was sent to all edges edgeImitator.expectMessageAmount(1); doDelete("/api/dashboard/" + savedDashboard.getUuidId()) .andExpect(status().isOk()); - Assert.assertFalse(edgeImitator.waitForMessages(1)); + Assert.assertTrue(edgeImitator.waitForMessages(1)); // create dashboard #2 and assign to edge edgeImitator.expectMessageAmount(1); diff --git a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java index 15d5b5c809..1c9a6359d6 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java @@ -83,6 +83,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @DaoSqlTest public class DeviceEdgeTest extends AbstractEdgeTest { + private static final String DEFAULT_DEVICE_TYPE = "default"; + @Test public void testDevices() throws Exception { // create device and assign to edge; update device @@ -100,15 +102,15 @@ public class DeviceEdgeTest extends AbstractEdgeTest { Assert.assertEquals(savedDevice.getUuidId().getMostSignificantBits(), deviceUpdateMsg.getIdMSB()); Assert.assertEquals(savedDevice.getUuidId().getLeastSignificantBits(), deviceUpdateMsg.getIdLSB()); - // delete device - no messages expected + // delete device - message expected, message send to all edges edgeImitator.expectMessageAmount(1); doDelete("/api/device/" + savedDevice.getUuidId()) .andExpect(status().isOk()); - Assert.assertFalse(edgeImitator.waitForMessages(1)); + Assert.assertTrue(edgeImitator.waitForMessages(1)); // create device #2 and assign to edge edgeImitator.expectMessageAmount(2); - savedDevice = saveDevice("Edge Device 3", "Default"); + savedDevice = saveDevice("Edge Device 3", DEFAULT_DEVICE_TYPE); doPost("/api/edge/" + edge.getUuidId() + "/device/" + savedDevice.getUuidId(), Device.class); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -265,14 +267,16 @@ public class DeviceEdgeTest extends AbstractEdgeTest { public void testDeviceReachedMaximumAllowedOnCloud() throws Exception { // update tenant profile configuration loginSysAdmin(); - TenantProfile tenantProfile = doGet("/api/tenantProfile/" + savedTenant.getTenantProfileId().getId(), TenantProfile.class); + TenantProfile tenantProfile = doGet("/api/tenantProfile/" + tenantProfileId.getId(), TenantProfile.class); DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration(); profileConfiguration.setMaxDevices(1); tenantProfile.getProfileData().setConfiguration(profileConfiguration); doPost("/api/tenantProfile/", tenantProfile, TenantProfile.class); + edgeImitator.expectMessageAmount(2); loginTenantAdmin(); + Assert.assertTrue(edgeImitator.waitForMessages()); UUID uuid = Uuids.timeBased(); @@ -281,7 +285,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest { deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); deviceUpdateMsgBuilder.setName("Edge Device"); - deviceUpdateMsgBuilder.setType("default"); + deviceUpdateMsgBuilder.setType(DEFAULT_DEVICE_TYPE); deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); @@ -479,7 +483,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest { @Test public void testSendDeviceToCloudWithNameThatAlreadyExistsOnCloud() throws Exception { String deviceOnCloudName = StringUtils.randomAlphanumeric(15); - Device deviceOnCloud = saveDevice(deviceOnCloudName, "Default"); + Device deviceOnCloud = saveDevice(deviceOnCloudName, DEFAULT_DEVICE_TYPE); UUID uuid = Uuids.timeBased(); diff --git a/application/src/test/java/org/thingsboard/server/edge/EntityViewEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/EntityViewEdgeTest.java index 234a9473fa..75386dbb17 100644 --- a/application/src/test/java/org/thingsboard/server/edge/EntityViewEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/EntityViewEdgeTest.java @@ -94,11 +94,11 @@ public class EntityViewEdgeTest extends AbstractEdgeTest { Assert.assertEquals(entityViewUpdateMsg.getIdMSB(), savedEntityView.getUuidId().getMostSignificantBits()); Assert.assertEquals(entityViewUpdateMsg.getIdLSB(), savedEntityView.getUuidId().getLeastSignificantBits()); - // delete entity view - no messages expected + // delete entity view - message expected, it was sent to all edges edgeImitator.expectMessageAmount(1); doDelete("/api/entityView/" + savedEntityView.getUuidId()) .andExpect(status().isOk()); - Assert.assertFalse(edgeImitator.waitForMessages(1)); + Assert.assertTrue(edgeImitator.waitForMessages(1)); // create entity view #2 and assign to edge edgeImitator.expectMessageAmount(1); diff --git a/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java index b810810225..83b2370cf0 100644 --- a/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java @@ -19,6 +19,9 @@ import com.google.protobuf.AbstractMessage; import org.junit.Assert; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.metadata.TbGetAttributesNode; +import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.rule.RuleChain; @@ -33,6 +36,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UplinkMsg; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -81,7 +85,7 @@ public class RuleChainEdgeTest extends AbstractEdgeTest { edgeImitator.expectMessageAmount(1); doDelete("/api/ruleChain/" + savedRuleChain.getUuidId()) .andExpect(status().isOk()); - Assert.assertFalse(edgeImitator.waitForMessages(1)); + Assert.assertTrue(edgeImitator.waitForMessages(5)); } @Test @@ -136,24 +140,30 @@ public class RuleChainEdgeTest extends AbstractEdgeTest { Assert.assertEquals(ruleChainId, receivedRuleChainId); } - private void createRuleChainMetadata(RuleChain ruleChain) throws Exception { + private void createRuleChainMetadata(RuleChain ruleChain) { RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); ruleChainMetaData.setRuleChainId(ruleChain.getId()); RuleNode ruleNode1 = new RuleNode(); ruleNode1.setName("name1"); - ruleNode1.setType("type1"); - ruleNode1.setConfiguration(JacksonUtil.toJsonNode("\"key1\": \"val1\"")); + ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); + ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); + TbGetAttributesNodeConfiguration configuration = new TbGetAttributesNodeConfiguration(); + configuration.setFetchTo(TbMsgSource.METADATA); + configuration.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); + ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration)); RuleNode ruleNode2 = new RuleNode(); ruleNode2.setName("name2"); - ruleNode2.setType("type2"); - ruleNode2.setConfiguration(JacksonUtil.toJsonNode("\"key2\": \"val2\"")); + ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); + ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); + ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration)); RuleNode ruleNode3 = new RuleNode(); ruleNode3.setName("name3"); - ruleNode3.setType("type3"); - ruleNode3.setConfiguration(JacksonUtil.toJsonNode("\"key3\": \"val3\"")); + ruleNode3.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); + ruleNode3.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version()); + ruleNode3.setConfiguration(JacksonUtil.valueToTree(configuration)); List ruleNodes = new ArrayList<>(); ruleNodes.add(ruleNode1); @@ -172,11 +182,12 @@ public class RuleChainEdgeTest extends AbstractEdgeTest { @Test public void testSetRootRuleChain() throws Exception { // create rule chain - edgeImitator.expectMessageAmount(1); RuleChain ruleChain = new RuleChain(); ruleChain.setName("Edge New Root Rule Chain"); ruleChain.setType(RuleChainType.EDGE); RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class); + + edgeImitator.expectMessageAmount(1); doPost("/api/edge/" + edge.getUuidId() + "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -211,6 +222,6 @@ public class RuleChainEdgeTest extends AbstractEdgeTest { edgeImitator.expectMessageAmount(1); doDelete("/api/ruleChain/" + savedRuleChain.getUuidId()) .andExpect(status().isOk()); - Assert.assertFalse(edgeImitator.waitForMessages(1)); + Assert.assertTrue(edgeImitator.waitForMessages(1)); } } diff --git a/application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java index 0cf1c2d149..2ee267b88c 100644 --- a/application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java @@ -215,7 +215,7 @@ public class TelemetryEdgeTest extends AbstractEdgeTest { @Test public void testAttributesUpdatedMsg_userEntity() throws Exception { - testAttributesUpdatedMsg(tenantAdmin.getId()); + testAttributesUpdatedMsg(tenantAdminUserId); } private void testAttributesUpdatedMsg(EntityId entityId) throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java index f376f50ac5..d1d555f691 100644 --- a/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java @@ -45,18 +45,18 @@ public class UserEdgeTest extends AbstractEdgeTest { @Test public void testCreateUpdateDeleteTenantUser() throws Exception { // create user - edgeImitator.expectMessageAmount(2); + edgeImitator.expectMessageAmount(3); User newTenantAdmin = new User(); newTenantAdmin.setAuthority(Authority.TENANT_ADMIN); - newTenantAdmin.setTenantId(savedTenant.getId()); + newTenantAdmin.setTenantId(tenantId); newTenantAdmin.setEmail("tenantAdmin@thingsboard.org"); newTenantAdmin.setFirstName("Boris"); newTenantAdmin.setLastName("Johnson"); User savedTenantAdmin = createUser(newTenantAdmin, "tenant"); - Assert.assertTrue(edgeImitator.waitForMessages()); // wait 2 messages - user update msg and user credentials update msg - Optional latestMessageOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); - Assert.assertTrue(latestMessageOpt.isPresent()); - UserUpdateMsg userUpdateMsg = latestMessageOpt.get(); + Assert.assertTrue(edgeImitator.waitForMessages()); // wait 3 messages - user update msg and x2 user credentials update msgs + Optional userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); + Assert.assertTrue(userUpdateMsgOpt.isPresent()); + UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get(); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, userUpdateMsg.getMsgType()); Assert.assertEquals(savedTenantAdmin.getUuidId().getMostSignificantBits(), userUpdateMsg.getIdMSB()); Assert.assertEquals(savedTenantAdmin.getUuidId().getLeastSignificantBits(), userUpdateMsg.getIdLSB()); @@ -79,8 +79,11 @@ public class UserEdgeTest extends AbstractEdgeTest { Assert.assertEquals(savedTenantAdmin.getLastName(), userUpdateMsg.getLastName()); // update user credentials - edgeImitator.expectMessageAmount(1); + edgeImitator.expectMessageAmount(2); login(savedTenantAdmin.getEmail(), "tenant"); + Assert.assertTrue(edgeImitator.waitForMessages()); + + edgeImitator.expectMessageAmount(1); ChangePasswordRequest changePasswordRequest = new ChangePasswordRequest(); changePasswordRequest.setCurrentPassword("tenant"); changePasswordRequest.setNewPassword("newTenant"); @@ -93,9 +96,12 @@ public class UserEdgeTest extends AbstractEdgeTest { Assert.assertEquals(savedTenantAdmin.getUuidId().getLeastSignificantBits(), userCredentialsUpdateMsg.getUserIdLSB()); Assert.assertTrue(passwordEncoder.matches(changePasswordRequest.getNewPassword(), userCredentialsUpdateMsg.getPassword())); + edgeImitator.expectMessageAmount(2); + loginTenantAdmin(); + Assert.assertTrue(edgeImitator.waitForMessages()); + // delete user edgeImitator.expectMessageAmount(1); - login(tenantAdmin.getEmail(), "testPassword1"); doDelete("/api/user/" + savedTenantAdmin.getUuidId()) .andExpect(status().isOk()); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -123,19 +129,19 @@ public class UserEdgeTest extends AbstractEdgeTest { Assert.assertTrue(edgeImitator.waitForMessages()); // create user - edgeImitator.expectMessageAmount(2); + edgeImitator.expectMessageAmount(3); User customerUser = new User(); customerUser.setAuthority(Authority.CUSTOMER_USER); - customerUser.setTenantId(savedTenant.getId()); + customerUser.setTenantId(tenantId); customerUser.setCustomerId(savedCustomer.getId()); customerUser.setEmail("customerUser@thingsboard.org"); customerUser.setFirstName("John"); customerUser.setLastName("Edwards"); User savedCustomerUser = createUser(customerUser, "customer"); - Assert.assertTrue(edgeImitator.waitForMessages()); // wait 2 messages - user update msg and user credentials update msg - Optional latestMessageOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); - Assert.assertTrue(latestMessageOpt.isPresent()); - UserUpdateMsg userUpdateMsg = latestMessageOpt.get(); + Assert.assertTrue(edgeImitator.waitForMessages()); // wait 3 messages - user update msg and x2 user credentials update msgs + Optional userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); + Assert.assertTrue(userUpdateMsgOpt.isPresent()); + UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get(); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, userUpdateMsg.getMsgType()); Assert.assertEquals(savedCustomerUser.getUuidId().getMostSignificantBits(), userUpdateMsg.getIdMSB()); Assert.assertEquals(savedCustomerUser.getUuidId().getLeastSignificantBits(), userUpdateMsg.getIdLSB()); @@ -158,8 +164,11 @@ public class UserEdgeTest extends AbstractEdgeTest { Assert.assertEquals(savedCustomerUser.getLastName(), userUpdateMsg.getLastName()); // update user credentials - edgeImitator.expectMessageAmount(1); + edgeImitator.expectMessageAmount(2); login(savedCustomerUser.getEmail(), "customer"); + Assert.assertTrue(edgeImitator.waitForMessages()); + + edgeImitator.expectMessageAmount(1); ChangePasswordRequest changePasswordRequest = new ChangePasswordRequest(); changePasswordRequest.setCurrentPassword("customer"); changePasswordRequest.setNewPassword("newCustomer"); @@ -172,9 +181,12 @@ public class UserEdgeTest extends AbstractEdgeTest { Assert.assertEquals(savedCustomerUser.getUuidId().getLeastSignificantBits(), userCredentialsUpdateMsg.getUserIdLSB()); Assert.assertTrue(passwordEncoder.matches(changePasswordRequest.getNewPassword(), userCredentialsUpdateMsg.getPassword())); + edgeImitator.expectMessageAmount(2); + loginTenantAdmin(); + Assert.assertTrue(edgeImitator.waitForMessages()); + // delete user edgeImitator.expectMessageAmount(1); - login(tenantAdmin.getEmail(), "testPassword1"); doDelete("/api/user/" + savedCustomerUser.getUuidId()) .andExpect(status().isOk()); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -191,8 +203,8 @@ public class UserEdgeTest extends AbstractEdgeTest { public void testSendUserCredentialsRequestToCloud() throws Exception { UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); UserCredentialsRequestMsg.Builder userCredentialsRequestMsgBuilder = UserCredentialsRequestMsg.newBuilder(); - userCredentialsRequestMsgBuilder.setUserIdMSB(tenantAdmin.getId().getId().getMostSignificantBits()); - userCredentialsRequestMsgBuilder.setUserIdLSB(tenantAdmin.getId().getId().getLeastSignificantBits()); + userCredentialsRequestMsgBuilder.setUserIdMSB(tenantAdminUserId.getId().getMostSignificantBits()); + userCredentialsRequestMsgBuilder.setUserIdLSB(tenantAdminUserId.getId().getLeastSignificantBits()); testAutoGeneratedCodeByProtobuf(userCredentialsRequestMsgBuilder); uplinkMsgBuilder.addUserCredentialsRequestMsg(userCredentialsRequestMsgBuilder.build()); @@ -207,16 +219,16 @@ public class UserEdgeTest extends AbstractEdgeTest { AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof UserCredentialsUpdateMsg); UserCredentialsUpdateMsg userCredentialsUpdateMsg = (UserCredentialsUpdateMsg) latestMessage; - Assert.assertEquals(tenantAdmin.getId().getId().getMostSignificantBits(), userCredentialsUpdateMsg.getUserIdMSB()); - Assert.assertEquals(tenantAdmin.getId().getId().getLeastSignificantBits(), userCredentialsUpdateMsg.getUserIdLSB()); + Assert.assertEquals(tenantAdminUserId.getId().getMostSignificantBits(), userCredentialsUpdateMsg.getUserIdMSB()); + Assert.assertEquals(tenantAdminUserId.getId().getLeastSignificantBits(), userCredentialsUpdateMsg.getUserIdLSB()); } @Test public void sendUserCredentialsRequest() throws Exception { UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); UserCredentialsRequestMsg.Builder userCredentialsRequestMsgBuilder = UserCredentialsRequestMsg.newBuilder(); - userCredentialsRequestMsgBuilder.setUserIdMSB(tenantAdmin.getId().getId().getMostSignificantBits()); - userCredentialsRequestMsgBuilder.setUserIdLSB(tenantAdmin.getId().getId().getLeastSignificantBits()); + userCredentialsRequestMsgBuilder.setUserIdMSB(tenantAdminUserId.getId().getMostSignificantBits()); + userCredentialsRequestMsgBuilder.setUserIdLSB(tenantAdminUserId.getId().getLeastSignificantBits()); testAutoGeneratedCodeByProtobuf(userCredentialsRequestMsgBuilder); uplinkMsgBuilder.addUserCredentialsRequestMsg(userCredentialsRequestMsgBuilder.build()); @@ -231,8 +243,8 @@ public class UserEdgeTest extends AbstractEdgeTest { AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof UserCredentialsUpdateMsg); UserCredentialsUpdateMsg userCredentialsUpdateMsg = (UserCredentialsUpdateMsg) latestMessage; - Assert.assertEquals(userCredentialsUpdateMsg.getUserIdMSB(), tenantAdmin.getId().getId().getMostSignificantBits()); - Assert.assertEquals(userCredentialsUpdateMsg.getUserIdLSB(), tenantAdmin.getId().getId().getLeastSignificantBits()); + Assert.assertEquals(userCredentialsUpdateMsg.getUserIdMSB(), tenantAdminUserId.getId().getMostSignificantBits()); + Assert.assertEquals(userCredentialsUpdateMsg.getUserIdLSB(), tenantAdminUserId.getId().getLeastSignificantBits()); testAutoGeneratedCodeByProtobuf(userCredentialsUpdateMsg); } diff --git a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java index 0edf070aef..67db3e6b27 100644 --- a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java +++ b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java @@ -177,16 +177,16 @@ public class EdgeImitator { result.add(saveDownlinkMsg(adminSettingsUpdateMsg)); } } - if (downlinkMsg.getDeviceUpdateMsgCount() > 0) { - for (DeviceUpdateMsg deviceUpdateMsg : downlinkMsg.getDeviceUpdateMsgList()) { - result.add(saveDownlinkMsg(deviceUpdateMsg)); - } - } if (downlinkMsg.getDeviceProfileUpdateMsgCount() > 0) { for (DeviceProfileUpdateMsg deviceProfileUpdateMsg : downlinkMsg.getDeviceProfileUpdateMsgList()) { result.add(saveDownlinkMsg(deviceProfileUpdateMsg)); } } + if (downlinkMsg.getDeviceUpdateMsgCount() > 0) { + for (DeviceUpdateMsg deviceUpdateMsg : downlinkMsg.getDeviceUpdateMsgList()) { + result.add(saveDownlinkMsg(deviceUpdateMsg)); + } + } if (downlinkMsg.getDeviceCredentialsUpdateMsgCount() > 0) { for (DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg : downlinkMsg.getDeviceCredentialsUpdateMsgList()) { result.add(saveDownlinkMsg(deviceCredentialsUpdateMsg)); @@ -293,6 +293,9 @@ public class EdgeImitator { if (downlinkMsg.hasEdgeConfiguration()) { result.add(saveDownlinkMsg(downlinkMsg.getEdgeConfiguration())); } + if (downlinkMsg.hasSyncCompletedMsg()) { + result.add(saveDownlinkMsg(downlinkMsg.getSyncCompletedMsg())); + } return Futures.allAsList(result); } diff --git a/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java b/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java index f01f4fae04..a88f25d5bf 100644 --- a/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/device/provision/DeviceProvisionServiceTest.java @@ -71,8 +71,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -; - @Slf4j @RunWith(SpringRunner.class) @ContextConfiguration(classes = DeviceProvisionServiceImpl.class) diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java index 487783b454..229ea5d9f9 100644 --- a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.User; 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.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.dao.alarm.AlarmService; @@ -42,7 +43,7 @@ import java.util.UUID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -87,7 +88,7 @@ public class DefaultTbAlarmServiceTest { .build()); service.save(alarm, new User()); - verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any()); + verify(notificationEntityService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.ADDED), any()); verify(alarmSubscriptionService, times(1)).createAlarm(any()); } @@ -95,11 +96,11 @@ public class DefaultTbAlarmServiceTest { public void testAck() throws ThingsboardException { var alarm = new Alarm(); when(alarmSubscriptionService.acknowledgeAlarm(any(), any(), anyLong())) - .thenReturn(AlarmApiCallResult.builder().successful(true).modified(true).build()); + .thenReturn(AlarmApiCallResult.builder().successful(true).modified(true).alarm(new AlarmInfo()).build()); service.ack(alarm, new User(new UserId(UUID.randomUUID()))); verify(alarmCommentService, times(1)).saveAlarmComment(any(), any(), any()); - verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any()); + verify(notificationEntityService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.ALARM_ACK), any()); verify(alarmSubscriptionService, times(1)).acknowledgeAlarm(any(), any(), anyLong()); } @@ -108,11 +109,11 @@ public class DefaultTbAlarmServiceTest { var alarm = new Alarm(); alarm.setAcknowledged(true); when(alarmSubscriptionService.clearAlarm(any(), any(), anyLong(), any())) - .thenReturn(AlarmApiCallResult.builder().successful(true).cleared(true).build()); + .thenReturn(AlarmApiCallResult.builder().successful(true).cleared(true).alarm(new AlarmInfo()).build()); service.clear(alarm, new User(new UserId(UUID.randomUUID()))); verify(alarmCommentService, times(1)).saveAlarmComment(any(), any(), any()); - verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any()); + verify(notificationEntityService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.ALARM_CLEAR), any()); verify(alarmSubscriptionService, times(1)).clearAlarm(any(), any(), anyLong(), any()); } @@ -120,7 +121,7 @@ public class DefaultTbAlarmServiceTest { public void testDelete() { service.delete(new Alarm(), new User()); - verify(notificationEntityService, times(1)).notifyDeleteAlarm(any(), any(), any(), any(), any(), any(), anyString()); + verify(notificationEntityService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.DELETED), any()); verify(alarmSubscriptionService, times(1)).deleteAlarm(any(), any()); } -} \ No newline at end of file +} diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java index 29d9a6ebb8..50055b8d1a 100644 --- a/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/entitiy/alarmComment/DefaultTbAlarmCommentServiceTest.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmCommentType; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.UserId; @@ -81,7 +82,7 @@ public class DefaultTbAlarmCommentServiceTest { when(alarmCommentService.createOrUpdateAlarmComment(Mockito.any(), eq(alarmComment))).thenReturn(alarmComment); service.saveAlarmComment(alarm, alarmComment, new User()); - verify(notificationEntityService, times(1)).notifyAlarmComment(any(), any(), any(), any()); + verify(notificationEntityService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.ADDED_COMMENT), any(), any()); } @Test @@ -95,7 +96,7 @@ public class DefaultTbAlarmCommentServiceTest { when(alarmCommentService.saveAlarmComment(Mockito.any(), eq(alarmComment))).thenReturn(alarmComment); service.deleteAlarmComment(new Alarm(alarmId), alarmComment, new User()); - verify(notificationEntityService, times(1)).notifyAlarmComment(any(), any(), any(), any()); + verify(notificationEntityService, times(1)).logEntityAction(any(), any(), any(), any(), eq(ActionType.DELETED_COMMENT), any(), any()); } @Test diff --git a/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java b/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java index 8962a35187..e81aaed39d 100644 --- a/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java +++ b/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java @@ -80,8 +80,6 @@ public interface TbClusterService extends TbQueueClusterService { void onDeviceUpdated(Device device, Device old); - void onDeviceUpdated(Device device, Device old, boolean notifyEdge); - void onDeviceDeleted(Device device, TbQueueCallback callback); void onResourceChange(TbResource resource, TbQueueCallback callback); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeSynchronizationManager.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeSynchronizationManager.java new file mode 100644 index 0000000000..8cce581098 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeSynchronizationManager.java @@ -0,0 +1,23 @@ +/** + * 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.dao.edge; + +public interface EdgeSynchronizationManager { + + ThreadLocal getSync(); + + boolean isSync(); +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java index e1f16be1ed..47a7423191 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java @@ -39,7 +39,7 @@ public interface UserService extends EntityDaoService { User findUserByTenantIdAndEmail(TenantId tenantId, String email); - User saveUser(User user); + User saveUser(TenantId tenantId, User user); UserCredentials findUserCredentialsByUserId(TenantId tenantId, UserId userId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java index 756c30690f..be54b70f18 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java @@ -15,24 +15,33 @@ */ package org.thingsboard.server.common.data.edge; +import lombok.Getter; + +@Getter public enum EdgeEventType { - DASHBOARD, - ASSET, - DEVICE, - DEVICE_PROFILE, - ASSET_PROFILE, - ENTITY_VIEW, - ALARM, - RULE_CHAIN, - RULE_CHAIN_METADATA, - EDGE, - USER, - CUSTOMER, - RELATION, - TENANT, - WIDGETS_BUNDLE, - WIDGET_TYPE, - ADMIN_SETTINGS, - OTA_PACKAGE, - QUEUE + DASHBOARD(false), + ASSET(false), + DEVICE(false), + DEVICE_PROFILE(true), + ASSET_PROFILE(true), + ENTITY_VIEW(false), + ALARM(false), + RULE_CHAIN(false), + RULE_CHAIN_METADATA(false), + EDGE(false), + USER(true), + CUSTOMER(true), + RELATION(true), + TENANT(true), + WIDGETS_BUNDLE(true), + WIDGET_TYPE(true), + ADMIN_SETTINGS(true), + OTA_PACKAGE(true), + QUEUE(true); + + private final boolean allEdgesRelated; + + EdgeEventType(boolean allEdgesRelated) { + this.allEdgesRelated = allEdgesRelated; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 96342aca2f..85428e14a9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; import org.thingsboard.server.common.data.alarm.EntityAlarm; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; @@ -56,6 +57,9 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationsSearchParameters; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.EntityService; +import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.service.DataValidator; @@ -90,7 +94,12 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Override public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) { validateAlarmRequest(request); - return withPropagated(alarmDao.updateAlarm(request)); + AlarmApiCallResult result = withPropagated(alarmDao.updateAlarm(request)); + if (result.getAlarm() != null) { + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getAlarm().getTenantId()).entity(result) + .entityId(result.getAlarm().getId()).build()); + } + return result; } @Override @@ -112,17 +121,31 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ if (!result.isSuccessful() && !alarmCreationEnabled) { throw new ApiUsageLimitsExceededException("Alarms creation is disabled"); } + if (result.getAlarm() != null) { + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getAlarm().getTenantId()) + .entityId(result.getAlarm().getId()).added(true).build()); + } return withPropagated(result); } @Override public AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) { - return withPropagated(alarmDao.acknowledgeAlarm(tenantId, alarmId, ackTs)); + var result = withPropagated(alarmDao.acknowledgeAlarm(tenantId, alarmId, ackTs)); + if (result.getAlarm() != null) { + eventPublisher.publishEvent(ActionEntityEvent.builder().tenantId(tenantId).entityId(result.getAlarm().getId()) + .actionType(ActionType.ALARM_ACK).build()); + } + return result; } @Override public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId alarmId, long clearTs, JsonNode details) { - return withPropagated(alarmDao.clearAlarm(tenantId, alarmId, clearTs, details)); + var result = withPropagated(alarmDao.clearAlarm(tenantId, alarmId, clearTs, details)); + if (result.getAlarm() != null) { + eventPublisher.publishEvent(ActionEntityEvent.builder().tenantId(tenantId).entityId(result.getAlarm().getId()) + .actionType(ActionType.ALARM_CLEAR).build()); + } + return result; } @Override @@ -188,6 +211,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } else { deleteEntityRelations(tenantId, alarm.getId()); alarmDao.removeById(tenantId, alarm.getUuidId()); + eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId) + .entityId(alarmId).entity(alarm).build()); return AlarmApiCallResult.builder().alarm(alarm).deleted(true).successful(true).build(); } } @@ -300,12 +325,22 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Override public AlarmApiCallResult assignAlarm(TenantId tenantId, AlarmId alarmId, UserId assigneeId, long assignTime) { - return withPropagated(alarmDao.assignAlarm(tenantId, alarmId, assigneeId, assignTime)); + var result = withPropagated(alarmDao.assignAlarm(tenantId, alarmId, assigneeId, assignTime)); + if (result.getAlarm() != null) { + eventPublisher.publishEvent(ActionEntityEvent.builder().tenantId(tenantId).entityId(result.getAlarm().getId()) + .actionType(ActionType.ALARM_ASSIGNED).build()); + } + return result; } @Override public AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long unassignTime) { - return withPropagated(alarmDao.unassignAlarm(tenantId, alarmId, unassignTime)); + var result = withPropagated(alarmDao.unassignAlarm(tenantId, alarmId, unassignTime)); + if (result.getAlarm() != null) { + eventPublisher.publishEvent(ActionEntityEvent.builder().tenantId(tenantId).entityId(result.getAlarm().getId()) + .actionType(ActionType.ALARM_UNASSIGNED).build()); + } + return result; } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java index eb297e0049..b3bc89c34d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java @@ -33,6 +33,8 @@ 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.dao.entity.AbstractCachedEntityService; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -118,6 +120,8 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService sync = new ThreadLocal<>(); + + @Override + public boolean isSync() { + Boolean sync = this.sync.get(); + return sync != null && sync; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index 31b446fbd5..127741e8d0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeInfo; import org.thingsboard.server.common.data.edge.EdgeSearchQuery; @@ -53,6 +54,7 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; +import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; @@ -179,6 +181,8 @@ public class EdgeServiceImpl extends AbstractCachedEntityService cache; - @Autowired - private ApplicationEventPublisher eventPublisher; - protected void publishEvictEvent(E event) { if (TransactionSynchronizationManager.isActualTransactionActive()) { eventPublisher.publishEvent(event); diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java index 6d4ac9f97b..17cd7a15d4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.entity; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.StringUtils; @@ -43,6 +44,9 @@ public abstract class AbstractEntityService { public static final String INCORRECT_EDGE_ID = "Incorrect edgeId "; public static final String INCORRECT_PAGE_LINK = "Incorrect page link "; + @Autowired + protected ApplicationEventPublisher eventPublisher; + @Lazy @Autowired protected RelationService relationService; @@ -113,7 +117,7 @@ public abstract class AbstractEntityService { List entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(tenantId, entityId); if (entityViews != null && !entityViews.isEmpty()) { EntityView entityView = entityViews.get(0); - Boolean relationExists = relationService.checkRelation( + boolean relationExists = relationService.checkRelation( tenantId, edgeId, entityView.getId(), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE ); @@ -122,5 +126,4 @@ public abstract class AbstractEntityService { } } } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java index 6433db5ae8..2e5ee9071b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; @@ -43,6 +44,9 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; +import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -104,6 +108,8 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService { + private final TenantId tenantId; + private final EntityId entityId; + private final EdgeId edgeId; + private final T entity; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/eventsourcing/RelationActionEvent.java b/dao/src/main/java/org/thingsboard/server/dao/eventsourcing/RelationActionEvent.java new file mode 100644 index 0000000000..81437cd781 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/eventsourcing/RelationActionEvent.java @@ -0,0 +1,28 @@ +/** + * 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.dao.eventsourcing; + +import lombok.Data; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.relation.EntityRelation; + +@Data +public class RelationActionEvent { + private final TenantId tenantId; + private final EntityRelation relation; + private final ActionType actionType; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/eventsourcing/SaveEntityEvent.java b/dao/src/main/java/org/thingsboard/server/dao/eventsourcing/SaveEntityEvent.java new file mode 100644 index 0000000000..205f592d43 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/eventsourcing/SaveEntityEvent.java @@ -0,0 +1,30 @@ +/** + * 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.dao.eventsourcing; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; + +@Builder +@Data +public class SaveEntityEvent { + private final TenantId tenantId; + private final T entity; + private final EntityId entityId; + private final Boolean added; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java b/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java index 2a784d7985..13f84161c8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java @@ -38,6 +38,8 @@ import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -77,10 +79,12 @@ public class BaseOtaPackageService extends AbstractCachedEntityService handleEvictEvent(EntityRelationEvent.from(relation)), MoreExecutors.directExecutor()); + future.addListener(() -> { + handleEvictEvent(EntityRelationEvent.from(relation)); + eventPublisher.publishEvent(new RelationActionEvent(tenantId, relation, ActionType.RELATION_ADD_OR_UPDATE)); + }, MoreExecutors.directExecutor()); return future; } @@ -188,6 +195,7 @@ public class BaseRelationService implements RelationService { var result = relationDao.deleteRelation(tenantId, relation); //TODO: evict cache only if the relation was deleted. Note: relationDao.deleteRelation requires improvement. publishEvictEvent(EntityRelationEvent.from(relation)); + eventPublisher.publishEvent(new RelationActionEvent(tenantId, relation, ActionType.RELATION_DELETED)); return result; } @@ -196,7 +204,10 @@ public class BaseRelationService implements RelationService { log.trace("Executing deleteRelationAsync [{}]", relation); validate(relation); var future = relationDao.deleteRelationAsync(tenantId, relation); - future.addListener(() -> handleEvictEvent(EntityRelationEvent.from(relation)), MoreExecutors.directExecutor()); + future.addListener(() -> { + handleEvictEvent(EntityRelationEvent.from(relation)); + eventPublisher.publishEvent(new RelationActionEvent(tenantId, relation, ActionType.RELATION_DELETED)); + }, MoreExecutors.directExecutor()); return future; } @@ -206,7 +217,9 @@ public class BaseRelationService implements RelationService { validate(from, to, relationType, typeGroup); var result = relationDao.deleteRelation(tenantId, from, to, relationType, typeGroup); //TODO: evict cache only if the relation was deleted. Note: relationDao.deleteRelation requires improvement. - publishEvictEvent(new EntityRelationEvent(from, to, relationType, typeGroup)); + EntityRelation entityRelation = new EntityRelation(from, to, relationType, typeGroup); + publishEvictEvent(EntityRelationEvent.from(entityRelation)); + eventPublisher.publishEvent(new RelationActionEvent(tenantId, entityRelation, ActionType.RELATION_DELETED)); return result; } @@ -657,5 +670,4 @@ public class BaseRelationService implements RelationService { handleEvictEvent(event); } } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 56e6969c9a..5e6b1e8009 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -27,10 +27,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbVersionedNode; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; @@ -54,16 +53,17 @@ 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.common.data.util.TbPair; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.EntityCountService; +import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.validator.RuleChainDataValidator; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -114,6 +114,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC if (ruleChain.getId() == null) { entityCountService.publishCountEntityEvictEvent(ruleChain.getTenantId(), EntityType.RULE_CHAIN); } + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedRuleChain.getTenantId()) + .entity(savedRuleChain).entityId(savedRuleChain.getId()).added(ruleChain.getId() == null).build()); return savedRuleChain; } catch (Exception e) { checkConstraintViolation(e, "rule_chain_external_id_unq_key", "Rule Chain with such external id already exists!"); @@ -259,6 +261,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC if (!relations.isEmpty()) { relationService.saveRelations(tenantId, relations); } + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entity(ruleChain).entityId(ruleChain.getId()).build()); return RuleChainUpdateResult.successful(updatedRuleNodes); } @@ -594,6 +597,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC log.warn("[{}] Failed to create ruleChain relation. Edge Id: [{}]", ruleChainId, edgeId); throw new RuntimeException(e); } + if (!ruleChainId.equals(edge.getRootRuleChainId())) { + eventPublisher.publishEvent(ActionEntityEvent.builder().tenantId(tenantId).edgeId(edgeId).entityId(ruleChainId) + .actionType(ActionType.ASSIGNED_TO_EDGE).build()); + } return ruleChain; } @@ -613,6 +620,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC log.warn("[{}] Failed to delete rule chain relation. Edge Id: [{}]", ruleChainId, edgeId); throw new RuntimeException(e); } + eventPublisher.publishEvent(ActionEntityEvent.builder().tenantId(tenantId).edgeId(edgeId).entityId(ruleChainId) + .actionType(ActionType.UNASSIGNED_FROM_EDGE).build()); return ruleChain; } @@ -726,6 +735,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC try { entityCountService.publishCountEntityEvictEvent(tenantId, EntityType.RULE_CHAIN); ruleChainDao.removeById(tenantId, ruleChainId.getId()); + eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(ruleChainId).build()); } catch (Exception t) { ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_default_rule_chain_device_profile")) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index 5dc32b8eed..5afcc49def 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -30,6 +30,7 @@ import org.springframework.transaction.annotation.Transactional; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityType; 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; import org.thingsboard.server.common.data.id.HasId; @@ -44,6 +45,9 @@ import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.EntityCountService; +import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -119,7 +123,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic } @Override - public User saveUser(User user) { + public User saveUser(TenantId tenantId, User user) { log.trace("Executing saveUser [{}]", user); userValidator.validate(user, User::getTenantId); if (!userLoginCaseSensitive) { @@ -135,6 +139,11 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic userCredentials.setAdditionalInfo(JacksonUtil.newObjectNode()); userCredentialsDao.save(user.getTenantId(), userCredentials); } + eventPublisher.publishEvent(SaveEntityEvent.builder() + .tenantId(tenantId == null ? TenantId.SYS_TENANT_ID : tenantId) + .entity(user) + .entityId(savedUser.getId()) + .added(user.getId() == null).build()); return savedUser; } @@ -163,7 +172,12 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic public UserCredentials saveUserCredentials(TenantId tenantId, UserCredentials userCredentials) { log.trace("Executing saveUserCredentials [{}]", userCredentials); userCredentialsValidator.validate(userCredentials, data -> tenantId); - return userCredentialsDao.save(tenantId, userCredentials); + UserCredentials result = userCredentialsDao.save(tenantId, userCredentials); + eventPublisher.publishEvent(ActionEntityEvent.builder() + .tenantId(tenantId) + .entityId(userCredentials.getUserId()) + .actionType(ActionType.CREDENTIALS_UPDATED).build()); + return result; } @Override @@ -222,7 +236,12 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic if (userCredentials.getPassword() != null) { updatePasswordHistory(userCredentials); } - return userCredentialsDao.save(tenantId, userCredentials); + UserCredentials result = userCredentialsDao.save(tenantId, userCredentials); + eventPublisher.publishEvent(ActionEntityEvent.builder() + .tenantId(tenantId) + .entityId(userCredentials.getUserId()) + .actionType(ActionType.CREDENTIALS_UPDATED).build()); + return result; } @Override @@ -237,6 +256,9 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic userDao.removeById(tenantId, userId.getId()); eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(userId)); countService.publishCountEntityEvictEvent(tenantId, EntityType.USER); + eventPublisher.publishEvent(DeleteEntityEvent.builder() + .tenantId(tenantId) + .entityId(userId).build()); } @Override @@ -340,7 +362,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic log.trace("Executing onUserLoginSuccessful [{}]", userId); User user = findUserById(tenantId, userId); resetFailedLoginAttempts(user); - saveUser(user); + saveUser(tenantId, user); } private void resetFailedLoginAttempts(User user) { @@ -361,7 +383,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic } ((ObjectNode) additionalInfo).put(LAST_LOGIN_TS, System.currentTimeMillis()); user.setAdditionalInfo(additionalInfo); - saveUser(user); + saveUser(tenantId, user); } @Override @@ -369,7 +391,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic log.trace("Executing onUserLoginIncorrectCredentials [{}]", userId); User user = findUserById(tenantId, userId); int failedLoginAttempts = increaseFailedLoginAttempts(user); - saveUser(user); + saveUser(tenantId, user); return failedLoginAttempts; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java index 1ac099b075..d121d18924 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.widget; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; @@ -27,6 +28,8 @@ import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeInfo; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.Validator; @@ -40,12 +43,16 @@ public class WidgetTypeServiceImpl implements WidgetTypeService { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_RESOURCE_ID = "Incorrect resourceId "; public static final String INCORRECT_BUNDLE_ALIAS = "Incorrect bundleAlias "; + @Autowired private WidgetTypeDao widgetTypeDao; @Autowired private DataValidator widgetTypeValidator; + @Autowired + protected ApplicationEventPublisher eventPublisher; + @Override public WidgetType findWidgetTypeById(TenantId tenantId, WidgetTypeId widgetTypeId) { log.trace("Executing findWidgetTypeById [{}]", widgetTypeId); @@ -64,7 +71,10 @@ public class WidgetTypeServiceImpl implements WidgetTypeService { public WidgetTypeDetails saveWidgetType(WidgetTypeDetails widgetTypeDetails) { log.trace("Executing saveWidgetType [{}]", widgetTypeDetails); widgetTypeValidator.validate(widgetTypeDetails, WidgetType::getTenantId); - return widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails); + WidgetTypeDetails result = widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails); + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId()) + .entityId(result.getId()).added(widgetTypeDetails.getId() == null).build()); + return result; } @Override @@ -72,6 +82,7 @@ public class WidgetTypeServiceImpl implements WidgetTypeService { log.trace("Executing deleteWidgetType [{}]", widgetTypeId); Validator.validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId); widgetTypeDao.removeById(tenantId, widgetTypeId.getId()); + eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(widgetTypeId).build()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java index 63a4ce0a77..6becfb460f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.widget; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; @@ -27,6 +28,8 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -53,6 +56,9 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService { @Autowired private DataValidator widgetsBundleValidator; + @Autowired + protected ApplicationEventPublisher eventPublisher; + @Override public WidgetsBundle findWidgetsBundleById(TenantId tenantId, WidgetsBundleId widgetsBundleId) { log.trace("Executing findWidgetsBundleById [{}]", widgetsBundleId); @@ -65,7 +71,10 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService { log.trace("Executing saveWidgetsBundle [{}]", widgetsBundle); widgetsBundleValidator.validate(widgetsBundle, WidgetsBundle::getTenantId); try { - return widgetsBundleDao.save(widgetsBundle.getTenantId(), widgetsBundle); + WidgetsBundle result = widgetsBundleDao.save(widgetsBundle.getTenantId(), widgetsBundle); + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId()) + .entityId(result.getId()).added(widgetsBundle.getId() == null).build()); + return result; } catch (Exception e) { AbstractCachedEntityService.checkConstraintViolation(e, "widgets_bundle_external_id_unq_key", "Widget Bundle with such external id already exists!"); throw e; @@ -81,6 +90,7 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService { throw new IncorrectParameterException("Unable to delete non-existent widgets bundle."); } widgetTypeService.deleteWidgetTypesByTenantIdAndBundleAlias(widgetsBundle.getTenantId(), widgetsBundle.getAlias()); + eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(widgetsBundleId).build()); widgetsBundleDao.removeById(tenantId, widgetsBundleId.getId()); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmCommentServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmCommentServiceTest.java index 22257242f5..8aa3eeb125 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmCommentServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmCommentServiceTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.AlarmCommentInfo; import org.thingsboard.server.common.data.alarm.AlarmCommentType; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -71,7 +72,7 @@ public class AlarmCommentServiceTest extends AbstractServiceTest { user.setEmail("tenant@thingsboard.org"); user.setFirstName("John"); user.setLastName("Brown"); - user = userService.saveUser(user); + user = userService.saveUser(TenantId.SYS_TENANT_ID, user); } @After diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java index 5b0981b44a..412056855e 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; @@ -358,7 +359,7 @@ public class AlarmServiceTest extends AbstractServiceTest { tenantUser.setEmail(TEST_TENANT_EMAIL); tenantUser.setFirstName(TEST_TENANT_FIRST_NAME); tenantUser.setLastName(TEST_TENANT_LAST_NAME); - tenantUser = userService.saveUser(tenantUser); + tenantUser = userService.saveUser(TenantId.SYS_TENANT_ID, tenantUser); Assert.assertNotNull(tenantUser); @@ -392,7 +393,7 @@ public class AlarmServiceTest extends AbstractServiceTest { tenantUser2.setEmail(2 + TEST_TENANT_EMAIL); tenantUser2.setFirstName(TEST_TENANT_FIRST_NAME); tenantUser2.setLastName(TEST_TENANT_LAST_NAME); - tenantUser2 = userService.saveUser(tenantUser2); + tenantUser2 = userService.saveUser(TenantId.SYS_TENANT_ID, tenantUser2); Assert.assertNotNull(tenantUser2); pageLink.setAssigneeId(tenantUser2.getId()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/EntityServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/EntityServiceTest.java index 22d6bfcd9c..f41e92a305 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/EntityServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/EntityServiceTest.java @@ -260,7 +260,7 @@ public class EntityServiceTest extends AbstractServiceTest { user.setAuthority(Authority.TENANT_ADMIN); user.setEmail(StringUtils.randomAlphabetic(10) + "@gmail.com"); user.setPhone(StringUtils.randomNumeric(10)); - user = userService.saveUser(user); + user = userService.saveUser(tenantId, user); users.add(user); createRelation(tenantId, "Contains", tenantId, user.getId()); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java index 5872a5846b..ce1a83514a 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java @@ -716,7 +716,7 @@ public class TenantServiceTest extends AbstractServiceTest { user.setFirstName("tenantAdmin"); user.setLastName("tenantAdmin"); user.setTenantId(tenant.getId()); - return userService.saveUser(user); + return userService.saveUser(TenantId.SYS_TENANT_ID, user); } private Tenant createAndSaveTenant(TenantProfile tenantProfile) { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/UserServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/UserServiceTest.java index fcd57e5b3a..c423147474 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/UserServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/UserServiceTest.java @@ -58,7 +58,7 @@ public class UserServiceTest extends AbstractServiceTest { tenantAdmin.setAuthority(Authority.TENANT_ADMIN); tenantAdmin.setTenantId(tenantId); tenantAdmin.setEmail("tenant@thingsboard.org"); - userService.saveUser(tenantAdmin); + userService.saveUser(TenantId.SYS_TENANT_ID, tenantAdmin); Customer customer = new Customer(); customer.setTenantId(tenantId); @@ -70,7 +70,7 @@ public class UserServiceTest extends AbstractServiceTest { customerUser.setTenantId(tenantId); customerUser.setCustomerId(savedCustomer.getId()); customerUser.setEmail("customer@thingsboard.org"); - customerUser = userService.saveUser(customerUser); + customerUser = userService.saveUser(tenantId, customerUser); userSettings = createUserSettings(customerUser.getId()); } @@ -114,7 +114,7 @@ public class UserServiceTest extends AbstractServiceTest { user.setAuthority(Authority.TENANT_ADMIN); user.setTenantId(tenantAdminUser.getTenantId()); user.setEmail("tenant2@thingsboard.org"); - User savedUser = userService.saveUser(user); + User savedUser = userService.saveUser(TenantId.SYS_TENANT_ID, user); Assert.assertNotNull(savedUser); Assert.assertNotNull(savedUser.getId()); Assert.assertTrue(savedUser.getCreatedTime() > 0); @@ -130,7 +130,7 @@ public class UserServiceTest extends AbstractServiceTest { savedUser.setFirstName("Joe"); savedUser.setLastName("Downs"); - userService.saveUser(savedUser); + userService.saveUser(TenantId.SYS_TENANT_ID, savedUser); savedUser = userService.findUserById(tenantId, savedUser.getId()); Assert.assertEquals("Joe", savedUser.getFirstName()); Assert.assertEquals("Downs", savedUser.getLastName()); @@ -143,7 +143,7 @@ public class UserServiceTest extends AbstractServiceTest { User tenantAdminUser = userService.findUserByEmail(tenantId, "tenant@thingsboard.org"); tenantAdminUser.setEmail("sysadmin@thingsboard.org"); Assertions.assertThrows(DataValidationException.class, () -> { - userService.saveUser(tenantAdminUser); + userService.saveUser(tenantId, tenantAdminUser); }); } @@ -152,7 +152,7 @@ public class UserServiceTest extends AbstractServiceTest { User tenantAdminUser = userService.findUserByEmail(tenantId, "tenant@thingsboard.org"); tenantAdminUser.setEmail("tenant_thingsboard.org"); Assertions.assertThrows(DataValidationException.class, () -> { - userService.saveUser(tenantAdminUser); + userService.saveUser(tenantId, tenantAdminUser); }); } @@ -161,7 +161,7 @@ public class UserServiceTest extends AbstractServiceTest { User tenantAdminUser = userService.findUserByEmail(tenantId, "tenant@thingsboard.org"); tenantAdminUser.setEmail(null); Assertions.assertThrows(DataValidationException.class, () -> { - userService.saveUser(tenantAdminUser); + userService.saveUser(tenantId, tenantAdminUser); }); } @@ -170,7 +170,7 @@ public class UserServiceTest extends AbstractServiceTest { User tenantAdminUser = userService.findUserByEmail(tenantId, "tenant@thingsboard.org"); tenantAdminUser.setTenantId(null); Assertions.assertThrows(DataValidationException.class, () -> { - userService.saveUser(tenantAdminUser); + userService.saveUser(tenantId, tenantAdminUser); }); } @@ -181,7 +181,7 @@ public class UserServiceTest extends AbstractServiceTest { user.setAuthority(Authority.TENANT_ADMIN); user.setTenantId(tenantAdminUser.getTenantId()); user.setEmail("tenant2@thingsboard.org"); - User savedUser = userService.saveUser(user); + User savedUser = userService.saveUser(TenantId.SYS_TENANT_ID, user); Assert.assertNotNull(savedUser); Assert.assertNotNull(savedUser.getId()); User foundUser = userService.findUserById(tenantId, savedUser.getId()); @@ -212,7 +212,7 @@ public class UserServiceTest extends AbstractServiceTest { user.setAuthority(Authority.TENANT_ADMIN); user.setTenantId(secondTenantId); user.setEmail("testTenant" + i + "@thingsboard.org"); - tenantAdmins.add(userService.saveUser(user)); + tenantAdmins.add(userService.saveUser(TenantId.SYS_TENANT_ID, user)); } List loadedTenantAdmins = new ArrayList<>(); @@ -252,7 +252,7 @@ public class UserServiceTest extends AbstractServiceTest { String email = email1 + suffix + "@thingsboard.org"; email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); user.setEmail(email); - tenantAdminsEmail1.add(userService.saveUser(user)); + tenantAdminsEmail1.add(userService.saveUser(TenantId.SYS_TENANT_ID, user)); } String email2 = "testEmail2"; @@ -266,7 +266,7 @@ public class UserServiceTest extends AbstractServiceTest { String email = email2 + suffix + "@thingsboard.org"; email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); user.setEmail(email); - tenantAdminsEmail2.add(userService.saveUser(user)); + tenantAdminsEmail2.add(userService.saveUser(TenantId.SYS_TENANT_ID, user)); } List loadedTenantAdminsEmail1 = new ArrayList<>(); @@ -343,7 +343,7 @@ public class UserServiceTest extends AbstractServiceTest { user.setTenantId(tenantId); user.setCustomerId(customerId); user.setEmail("testCustomer" + i + "@thingsboard.org"); - customerUsers.add(userService.saveUser(user)); + customerUsers.add(userService.saveUser(tenantId, user)); } List loadedCustomerUsers = new ArrayList<>(); @@ -390,7 +390,7 @@ public class UserServiceTest extends AbstractServiceTest { String email = email1 + suffix + "@thingsboard.org"; email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); user.setEmail(email); - customerUsersEmail1.add(userService.saveUser(user)); + customerUsersEmail1.add(userService.saveUser(tenantId, user)); } String email2 = "testEmail2"; @@ -405,7 +405,7 @@ public class UserServiceTest extends AbstractServiceTest { String email = email2 + suffix + "@thingsboard.org"; email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); user.setEmail(email); - customerUsersEmail2.add(userService.saveUser(user)); + customerUsersEmail2.add(userService.saveUser(tenantId, user)); } List loadedCustomerUsersEmail1 = new ArrayList<>(); From dd19109034ddaa14380e1b0d156eb6e0318e25fe Mon Sep 17 00:00:00 2001 From: rusikv Date: Mon, 7 Aug 2023 16:11:14 +0300 Subject: [PATCH 144/166] Add delete button to selection in alarm table --- ui-ngx/src/app/core/http/alarm.service.ts | 12 ++-- .../components/alarm/alarm-table-config.ts | 65 +++++++++++++++---- .../lib/alarms-table-widget.component.ts | 4 +- .../assets/locale/locale.constant-en_US.json | 3 + 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/ui-ngx/src/app/core/http/alarm.service.ts b/ui-ngx/src/app/core/http/alarm.service.ts index e03e9ab783..f0ce187341 100644 --- a/ui-ngx/src/app/core/http/alarm.service.ts +++ b/ui-ngx/src/app/core/http/alarm.service.ts @@ -52,12 +52,12 @@ export class AlarmService { return this.http.post('/api/alarm', alarm, defaultHttpOptionsFromConfig(config)); } - public ackAlarm(alarmId: string, config?: RequestConfig): Observable { - return this.http.post(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptionsFromConfig(config)); + public ackAlarm(alarmId: string, config?: RequestConfig): Observable { + return this.http.post(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptionsFromConfig(config)); } - public clearAlarm(alarmId: string, config?: RequestConfig): Observable { - return this.http.post(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptionsFromConfig(config)); + public clearAlarm(alarmId: string, config?: RequestConfig): Observable { + return this.http.post(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptionsFromConfig(config)); } public assignAlarm(alarmId: string, assigneeId: string, config?: RequestConfig): Observable { @@ -68,8 +68,8 @@ export class AlarmService { return this.http.delete(`/api/alarm/${alarmId}/assign`, defaultHttpOptionsFromConfig(config)); } - public deleteAlarm(alarmId: string, config?: RequestConfig): Observable { - return this.http.delete(`/api/alarm/${alarmId}`, defaultHttpOptionsFromConfig(config)); + public deleteAlarm(alarmId: string, config?: RequestConfig): Observable { + return this.http.delete(`/api/alarm/${alarmId}`, defaultHttpOptionsFromConfig(config)); } public getAlarms(query: AlarmQuery, diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index 646e05511f..15e3fb1952 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -162,14 +162,18 @@ export class AlarmTableConfig extends EntityTableConfig icon: 'done', isEnabled: true, onAction: ($event, entities) => this.ackAlarms($event, entities) - } - ) - this.groupActionDescriptors.push( + }, { name: this.translate.instant('alarm.clear'), icon: 'clear', isEnabled: true, onAction: ($event, entities) => this.clearAlarms($event, entities) + }, + { + name: this.translate.instant('alarm.delete'), + icon: 'delete', + isEnabled: true, + onAction: ($event, entities) => this.deleteAlarms($event, entities) } ) } @@ -318,12 +322,17 @@ export class AlarmTableConfig extends EntityTableConfig const unacknowledgedAlarms = alarms.filter(alarm => { return alarm.status === AlarmStatus.CLEARED_UNACK || alarm.status === AlarmStatus.ACTIVE_UNACK; }) + let title = ''; + let content = ''; if (!unacknowledgedAlarms.length) { - this.dialogService.alert(this.translate.instant('alarm.selected-alarms', {count: alarms.length}), - this.translate.instant('alarm.selected-alarms-are-acknowledged')).subscribe(); + title = this.translate.instant('alarm.selected-alarms', {count: alarms.length}); + content = this.translate.instant('alarm.selected-alarms-are-acknowledged'); + this.dialogService.alert( + title, + content).subscribe(); } else { - const title = this.translate.instant('alarm.aknowledge-alarms-title', {count: unacknowledgedAlarms.length}); - const content = this.translate.instant('alarm.aknowledge-alarms-text', {count: unacknowledgedAlarms.length}); + title = this.translate.instant('alarm.aknowledge-alarms-title', {count: unacknowledgedAlarms.length}); + content = this.translate.instant('alarm.aknowledge-alarms-text', {count: unacknowledgedAlarms.length}); this.dialogService.confirm( title, content, @@ -331,7 +340,7 @@ export class AlarmTableConfig extends EntityTableConfig this.translate.instant('action.yes') ).subscribe((res) => { if (res) { - const tasks: Observable[] = []; + const tasks: Observable[] = []; for (const alarm of unacknowledgedAlarms) { tasks.push(this.alarmService.ackAlarm(alarm.id.id)); } @@ -350,12 +359,18 @@ export class AlarmTableConfig extends EntityTableConfig const activeAlarms = alarms.filter(alarm => { return alarm.status === AlarmStatus.ACTIVE_ACK || alarm.status === AlarmStatus.ACTIVE_UNACK; }) + let title = ''; + let content = ''; if (!activeAlarms.length) { - this.dialogService.alert(this.translate.instant('alarm.selected-alarms', {count: alarms.length}), - this.translate.instant('alarm.selected-alarms-are-cleared')).subscribe(); + title = this.translate.instant('alarm.selected-alarms', {count: alarms.length}); + content = this.translate.instant('alarm.selected-alarms-are-cleared'); + this.dialogService.alert( + title, + content + ).subscribe(); } else { - const title = this.translate.instant('alarm.clear-alarms-title', {count: activeAlarms.length}); - const content = this.translate.instant('alarm.clear-alarms-text', {count: activeAlarms.length}); + title = this.translate.instant('alarm.clear-alarms-title', {count: activeAlarms.length}); + content = this.translate.instant('alarm.clear-alarms-text', {count: activeAlarms.length}); this.dialogService.confirm( title, content, @@ -363,7 +378,7 @@ export class AlarmTableConfig extends EntityTableConfig this.translate.instant('action.yes') ).subscribe((res) => { if (res) { - const tasks: Observable[] = []; + const tasks: Observable[] = []; for (const alarm of activeAlarms) { tasks.push(this.alarmService.clearAlarm(alarm.id.id)); } @@ -375,4 +390,28 @@ export class AlarmTableConfig extends EntityTableConfig } } + deleteAlarms($event: Event, alarms: Array) { + if ($event) { + $event.stopPropagation(); + } + const title = this.translate.instant('alarm.delete-alarms-title', {count: alarms.length}); + const content = this.translate.instant('alarm.delete-alarms-text', {count: alarms.length}); + this.dialogService.confirm( + title, + content, + this.translate.instant('action.no'), + this.translate.instant('action.yes') + ).subscribe((res) => { + if (res) { + const tasks: Observable[] = []; + for (const alarm of alarms) { + tasks.push(this.alarmService.deleteAlarm(alarm.id.id)); + } + forkJoin(tasks).subscribe(() => { + this.updateData(); + }); + } + }); + } + } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index f799ed43ff..57e8db694c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -927,7 +927,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, ).subscribe((res) => { if (res) { if (res) { - const tasks: Observable[] = []; + const tasks: Observable[] = []; for (const alarmId of alarmIds) { tasks.push(this.alarmService.ackAlarm(alarmId)); } @@ -983,7 +983,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, ).subscribe((res) => { if (res) { if (res) { - const tasks: Observable[] = []; + const tasks: Observable[] = []; for (const alarmId of alarmIds) { tasks.push(this.alarmService.clearAlarm(alarmId)); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 756cbb3ab6..ce134c1fd4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -512,6 +512,7 @@ "severity-indeterminate": "Indeterminate", "acknowledge": "Acknowledge", "clear": "Clear", + "delete": "Delete", "search": "Search alarms", "selected-alarms": "{ count, plural, =1 {1 alarm} other {# alarms} } selected", "no-data": "No data to display", @@ -527,6 +528,8 @@ "clear-alarms-text": "Are you sure you want to clear { count, plural, =1 {1 alarm} other {# alarms} }?", "clear-alarm-title": "Clear Alarm", "clear-alarm-text": "Are you sure you want to clear Alarm?", + "delete-alarms-title": "Delete { count, plural, =1 {1 alarm} other {# alarms} }", + "delete-alarms-text": "Are you sure you want to delete { count, plural, =1 {1 alarm} other {# alarms} }?", "selected-alarms-are-cleared": "Selected alarms are already cleared", "alarm-status-filter": "Alarm Status Filter", "alarm-filter-title": "Alarm Filter", From fe7846d1520ae4d06311ebcf6b54161a814f024b Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 19 Aug 2021 15:23:16 +0200 Subject: [PATCH 145/166] ui: event table: default interval is 15 minutes --- .../src/app/modules/home/components/event/event-table-config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index a9816a130c..8b0ea62abb 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -40,6 +40,7 @@ import { EventContentDialogData } from '@home/components/event/event-content-dialog.component'; import { isEqual, sortObjectKeys } from '@core/utils'; +import {historyInterval, MINUTE} from '@shared/models/time/time.models'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { ChangeDetectorRef, Injector, StaticProvider, ViewContainerRef } from '@angular/core'; import { ComponentPortal } from '@angular/cdk/portal'; @@ -89,6 +90,7 @@ export class EventTableConfig extends EntityTableConfig { this.loadDataOnInit = false; this.tableTitle = ''; this.useTimePageLink = true; + this.defaultTimewindowInterval = historyInterval(MINUTE * 15); this.detailsPanelEnabled = false; this.selectionEnabled = false; this.searchEnabled = false; From 33bab60954e67220e278bf4399daf440d464efca Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 19 Aug 2021 15:22:46 +0200 Subject: [PATCH 146/166] ui: event table: ts with ms --- .../src/app/modules/home/components/event/event-table-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index 8b0ea62abb..a07967f9c3 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -178,7 +178,7 @@ export class EventTableConfig extends EntityTableConfig { updateColumns(updateTableColumns: boolean = false): void { this.columns = []; this.columns.push( - new DateEntityTableColumn('createdTime', 'event.event-time', this.datePipe, '120px'), + new DateEntityTableColumn('createdTime', 'event.event-time', this.datePipe, '120px', 'yyyy-MM-dd HH:mm:ss.SSS'), new EntityTableColumn('server', 'event.server', '100px', (entity) => entity.body.server, entity => ({}), false)); switch (this.eventType) { From b7d522295810b59201614f3df28fc59eabc18e0d Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Tue, 8 Aug 2023 17:16:02 +0300 Subject: [PATCH 147/166] UI: Updated code style --- .../src/app/modules/home/components/event/event-table-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index a07967f9c3..eb3edb0baf 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -40,7 +40,7 @@ import { EventContentDialogData } from '@home/components/event/event-content-dialog.component'; import { isEqual, sortObjectKeys } from '@core/utils'; -import {historyInterval, MINUTE} from '@shared/models/time/time.models'; +import { historyInterval, MINUTE } from '@shared/models/time/time.models'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { ChangeDetectorRef, Injector, StaticProvider, ViewContainerRef } from '@angular/core'; import { ComponentPortal } from '@angular/cdk/portal'; From a1fb657b0c1835faab0a3a786f66cdf00c1d91b6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 8 Aug 2023 18:54:07 +0300 Subject: [PATCH 148/166] UI: Introduce timewindow style. Ability to embed widget title panel into widget template. Add pattern support for widget title, etc. --- .../json/system/widget_bundles/cards.json | 16 +-- .../src/app/core/services/dialog.service.ts | 15 +-- .../dynamic-component-factory.service.ts | 32 ++++-- ui-ngx/src/app/core/utils.ts | 20 ++-- .../dashboard-page.component.html | 4 - .../alarms-table-basic-config.component.html | 1 + .../alarms-table-basic-config.component.ts | 9 +- ...entities-table-basic-config.component.html | 1 + .../entities-table-basic-config.component.ts | 9 +- .../simple-card-basic-config.component.ts | 9 +- ...meseries-table-basic-config.component.html | 1 + ...timeseries-table-basic-config.component.ts | 9 +- .../value-card-basic-config.component.html | 1 + .../value-card-basic-config.component.ts | 9 +- .../chart/flot-basic-config.component.html | 1 + .../chart/flot-basic-config.component.ts | 9 +- .../timewindow-config-panel.component.html | 23 ++-- .../timewindow-config-panel.component.ts | 25 +++- .../timewindow-style-panel.component.html | 97 ++++++++++++++++ .../timewindow-style-panel.component.scss | 54 +++++++++ .../timewindow-style-panel.component.ts | 108 ++++++++++++++++++ .../config/timewindow-style.component.html | 25 ++++ .../config/timewindow-style.component.ts | 97 ++++++++++++++++ .../config/widget-config-components.module.ts | 6 + .../custom-dialog-container.component.ts | 11 +- .../widget/dialog/custom-dialog.service.ts | 11 +- .../widget/dynamic-widget.component.ts | 5 +- .../lib/alarms-table-widget.component.ts | 30 +---- .../cards/value-card-widget.component.html | 5 +- .../cards/value-card-widget.component.scss | 7 ++ .../lib/cards/value-card-widget.component.ts | 17 ++- .../lib/cards/value-card-widget.models.ts | 9 +- .../lib/entities-table-widget.component.ts | 38 ++---- .../widget/lib/json-input-widget.component.ts | 6 +- .../lib/multiple-input-widget.component.ts | 5 +- .../value-card-widget-settings.component.html | 1 + .../common/font-settings-panel.component.html | 6 + .../common/font-settings-panel.component.ts | 5 +- .../widget/widget-component.service.ts | 13 ++- .../widget/widget-config.component.html | 1 + .../widget/widget-config.component.ts | 6 +- .../widget/widget-container.component.html | 62 +++++----- .../widget/widget-container.component.scss | 28 ++--- .../widget/widget-preview.component.ts | 16 +-- .../components/widget/widget.component.ts | 23 ++-- .../home/models/dashboard-component.models.ts | 37 +++--- .../home/models/widget-component.models.ts | 75 +++++++++++- .../components/color-input.component.ts | 6 +- .../dialog/color-picker-dialog.component.ts | 13 ++- .../material-icons-dialog.component.html | 1 + .../dialog/material-icons-dialog.component.ts | 16 ++- .../app/shared/components/icon.component.ts | 2 +- .../json-form/json-form.component.ts | 12 +- .../shared/components/markdown.component.ts | 18 +-- .../material-icon-select.component.ts | 24 ++-- .../components/material-icons.component.html | 9 ++ .../components/material-icons.component.scss | 8 ++ .../components/material-icons.component.ts | 9 ++ .../components/time/timewindow.component.html | 32 ++---- .../components/time/timewindow.component.scss | 17 ++- .../components/time/timewindow.component.ts | 55 ++++++++- .../shared/models/widget-settings.models.ts | 28 ++++- ui-ngx/src/app/shared/models/widget.models.ts | 5 +- .../assets/locale/locale.constant-en_US.json | 13 ++- 64 files changed, 928 insertions(+), 338 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/timewindow-style-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/timewindow-style-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/timewindow-style-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/timewindow-style.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/timewindow-style.component.ts diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index 1289923667..b87f2c7b83 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -229,43 +229,43 @@ { "alias": "value_card", "name": "Value card", - "image": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyNyIgZmlsbD0ibm9uZSIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTI4IDEyNyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KIDxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2RfMTE0Ml8yMDM5NTQpIj4KICA8cmVjdCB4PSI1LjUiIHk9IjIuNSIgd2lkdGg9IjExNyIgaGVpZ2h0PSIxMTciIHJ4PSIyLjI5NDEiIGZpbGw9IiNmZmYiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPgogIDxwYXRoIGQ9Im0zMy42MDMgMjkuMjIxdi03LjY0NzFjMC0xLjU4NjgtMS4yODA4LTIuODY3Ni0yLjg2NzYtMi44Njc2cy0yLjg2NzcgMS4yODA4LTIuODY3NyAyLjg2NzZ2Ny42NDcxYy0xLjE1NjYgMC44Njk4LTEuOTExNyAyLjI2NTQtMS45MTE3IDMuODIzNSAwIDIuNjM4MiAyLjE0MTIgNC43Nzk0IDQuNzc5NCA0Ljc3OTRzNC43Nzk0LTIuMTQxMiA0Ljc3OTQtNC43Nzk0YzAtMS41NTgxLTAuNzU1MS0yLjk1MzctMS45MTE4LTMuODIzNXptLTMuODIzNS03LjY0NzFjMC0wLjUyNTcgMC40MzAyLTAuOTU1OSAwLjk1NTktMC45NTU5czAuOTU1OSAwLjQzMDIgMC45NTU5IDAuOTU1OWgtMC45NTU5djAuOTU1OWgwLjk1NTl2MS45MTE3aC0wLjk1NTl2MC45NTU5aDAuOTU1OXYxLjkxMThoLTEuOTExOHYtNS43MzUzeiIgZmlsbD0iIzU0NjlGRiIvPgogIDxnIGZpbGw9IiMwMDAiPgogICA8cGF0aCBkPSJtNTAuMTQxIDE5Ljc0MXY2LjUyMzhoLTEuMTE1N3YtNi41MjM4aDEuMTE1N3ptMi4wNDc3IDB2MC44OTYxaC01LjE5MzJ2LTAuODk2MWg1LjE5MzJ6bTIuNjAzMyA2LjYxMzVjLTAuMzU4NSAwLTAuNjgyNi0wLjA1ODMtMC45NzIzLTAuMTc0OC0wLjI4NjgtMC4xMTk1LTAuNTMxOC0wLjI4NTMtMC43MzQ5LTAuNDk3My0wLjIwMDEtMC4yMTIxLTAuMzU0LTAuNDYxNi0wLjQ2MTUtMC43NDgzLTAuMTA3NS0wLjI4NjgtMC4xNjEzLTAuNTk2LTAuMTYxMy0wLjkyNzV2LTAuMTc5M2MwLTAuMzc5MyAwLjA1NTMtMC43MjI4IDAuMTY1OC0xLjAzMDVzMC4yNjQzLTAuNTcwNiAwLjQ2MTUtMC43ODg2YzAuMTk3MS0wLjIyMTEgMC40MzAxLTAuMzg5OCAwLjY5OS0wLjUwNjMgMC4yNjg4LTAuMTE2NSAwLjU2MDEtMC4xNzQ4IDAuODczNy0wLjE3NDggMC4zNDY1IDAgMC42NDk3IDAuMDU4MyAwLjkwOTYgMC4xNzQ4czAuNDc1IDAuMjgwOCAwLjY0NTIgMC40OTI4YzAuMTczMyAwLjIwOTEgMC4zMDE3IDAuNDU4NiAwLjM4NTQgMC43NDgzIDAuMDg2NiAwLjI4OTggMC4xMjk5IDAuNjA5NCAwLjEyOTkgMC45NTg5djAuNDYxNWgtMy43NDU5di0wLjc3NTJoMi42Nzk1di0wLjA4NTFjLTZlLTMgLTAuMTk0Mi0wLjA0NDgtMC4zNzY0LTAuMTE2NS0wLjU0NjYtMC4wNjg3LTAuMTcwMy0wLjE3NDctMC4zMDc3LTAuMzE4MS0wLjQxMjMtMC4xNDM0LTAuMTA0NS0wLjMzNDYtMC4xNTY4LTAuNTczNi0wLjE1NjgtMC4xNzkyIDAtMC4zMzkgMC4wMzg4LTAuNDc5NCAwLjExNjUtMC4xMzc0IDAuMDc0Ny0wLjI1MjQgMC4xODM3LTAuMzQ1IDAuMzI3MXMtMC4xNjQzIDAuMzE2Ni0wLjIxNTEgMC41MTk4Yy0wLjA0NzggMC4yMDAxLTAuMDcxNyAwLjQyNTYtMC4wNzE3IDAuNjc2NXYwLjE3OTNjMCAwLjIxMjEgMC4wMjg0IDAuNDA5MiAwLjA4NTIgMC41OTE0IDAuMDU5NyAwLjE3OTMgMC4xNDYzIDAuMzM2MSAwLjI1OTggMC40NzA1IDAuMTEzNiAwLjEzNDQgMC4yNTEgMC4yNDA1IDAuNDEyMyAwLjMxODEgMC4xNjEzIDAuMDc0NyAwLjM0NSAwLjExMiAwLjU1MTEgMC4xMTIgMC4yNTk5IDAgMC40OTE0LTAuMDUyMiAwLjY5NDUtMC4xNTY4IDAuMjAzMS0wLjEwNDUgMC4zNzk0LTAuMjUyNCAwLjUyODctMC40NDM2bDAuNTY5MSAwLjU1MTJjLTAuMTA0NiAwLjE1MjMtMC4yNDA1IDAuMjk4Ny0wLjQwNzggMC40MzkxLTAuMTY3MyAwLjEzNzQtMC4zNzE5IDAuMjQ5NC0wLjYxMzggMC4zMzYtMC4yMzkgMC4wODY2LTAuNTE2OCAwLjEzLTAuODMzNCAwLjEzem00LjAwNTctMy45NTJ2My44NjIzaC0xLjA3OTh2LTQuODQ4MWgxLjAxNzFsMC4wNjI3IDAuOTg1OHptLTAuMTc0NyAxLjI1OTEtMC4zNjc1LTAuMDA0NWMwLTAuMzM0NiAwLjA0MTktMC42NDM3IDAuMTI1NS0wLjkyNzVzMC4yMDYxLTAuNTMwMiAwLjM2NzQtMC43MzkzYzAuMTYxMy0wLjIxMjEgMC4zNjE1LTAuMzc0OSAwLjYwMDQtMC40ODg0IDAuMjQyLTAuMTE2NSAwLjUyMTMtMC4xNzQ4IDAuODM3OS0wLjE3NDggMC4yMjExIDAgMC40MjI3IDAuMDMyOSAwLjYwNDkgMC4wOTg2IDAuMTg1MiAwLjA2MjcgMC4zNDUgMC4xNjI4IDAuNDc5NSAwLjMwMDIgMC4xMzc0IDAuMTM3NCAwLjI0MTkgMC4zMTM2IDAuMzEzNiAwLjUyODcgMC4wNzQ3IDAuMjE1MSAwLjExMiAwLjQ3NSAwLjExMiAwLjc3OTd2My4yMzA1aC0xLjA3OTh2LTMuMTM2NGMwLTAuMjM2LTAuMDM1OS0wLjQyMTItMC4xMDc2LTAuNTU1Ni0wLjA2ODctMC4xMzQ1LTAuMTY4Ny0wLjIzMDEtMC4zMDAyLTAuMjg2OC0wLjEyODQtMC4wNTk4LTAuMjgyMy0wLjA4OTYtMC40NjE1LTAuMDg5Ni0wLjIwMzEgMC0wLjM3NjQgMC4wMzg4LTAuNTE5NyAwLjExNjUtMC4xNDA0IDAuMDc3Ni0wLjI1NTQgMC4xODM3LTAuMzQ1MSAwLjMxODEtMC4wODk2IDAuMTM0NC0wLjE1NTMgMC4yODk4LTAuMTk3MSAwLjQ2NnMtMC4wNjI3IDAuMzY0NC0wLjA2MjcgMC41NjQ2em0zLjAwNjUtMC4yODY4LTAuNTA2MyAwLjExMmMwLTAuMjkyNyAwLjA0MDMtMC41NjkgMC4xMjEtMC44Mjg5IDAuMDgzNi0wLjI2MjkgMC4yMDQ2LTAuNDkyOSAwLjM2MjktMC42OSAwLjE2MTMtMC4yMDAyIDAuMzYtMC4zNTcgMC41OTU5LTAuNDcwNSAwLjIzNi0wLjExMzUgMC41MDY0LTAuMTcwMyAwLjgxMS0wLjE3MDMgMC4yNDggMCAwLjQ2OSAwLjAzNDQgMC42NjMyIDAuMTAzMSAwLjE5NzEgMC4wNjU3IDAuMzY0NCAwLjE3MDIgMC41MDE4IDAuMzEzNnMwLjI0MiAwLjMzMDEgMC4zMTM3IDAuNTYwMWMwLjA3MTcgMC4yMjcgMC4xMDc1IDAuNTAxOCAwLjEwNzUgMC44MjQ1djMuMTM2NGgtMS4wODQzdi0zLjE0MDljMC0wLjI0NS0wLjAzNTktMC40MzQ2LTAuMTA3Ni0wLjU2OTEtMC4wNjg3LTAuMTM0NC0wLjE2NzItMC4yMjctMC4yOTU3LTAuMjc3OC0wLjEyODQtMC4wNTM3LTAuMjgyMy0wLjA4MDYtMC40NjE1LTAuMDgwNi0wLjE2NzMgMC0wLjMxNTEgMC4wMzEzLTAuNDQzNiAwLjA5NDEtMC4xMjU0IDAuMDU5Ny0wLjIzMTUgMC4xNDQ4LTAuMzE4MSAwLjI1NTQtMC4wODY2IDAuMTA3NS0wLjE1MjQgMC4yMzE1LTAuMTk3MiAwLjM3MTktMC4wNDE4IDAuMTQwNC0wLjA2MjcgMC4yOTI3LTAuMDYyNyAwLjQ1N3ptNS4zMDk2LTEuMDI2MXY1Ljc4MDFoLTEuMDc5OHYtNi43MTIxaDAuOTk0N2wwLjA4NTEgMC45MzJ6bTMuMTU4OSAxLjQ0NzN2MC4wOTQxYzAgMC4zNTI1LTAuMDQxOCAwLjY3OTYtMC4xMjU0IDAuOTgxMy0wLjA4MDcgMC4yOTg3LTAuMjAxNyAwLjU2LTAuMzYzIDAuNzg0MS0wLjE1ODMgMC4yMjEtMC4zNTM5IDAuMzkyOC0wLjU4NjkgMC41MTUzLTAuMjMzIDAuMTIyNC0wLjUwMTkgMC4xODM3LTAuODA2NiAwLjE4MzctMC4zMDE3IDAtMC41NjYtMC4wNTUzLTAuNzkzLTAuMTY1OC0wLjIyNDEtMC4xMTM1LTAuNDEzOC0wLjI3MzMtMC41NjkxLTAuNDc5NS0wLjE1NTMtMC4yMDYxLTAuMjgwOC0wLjQ0OC0wLjM3NjQtMC43MjU4LTAuMDkyNi0wLjI4MDgtMC4xNTgzLTAuNTg4NS0wLjE5NzEtMC45MjMxdi0wLjM2MjljMC4wMzg4LTAuMzU1NSAwLjEwNDUtMC42NzgxIDAuMTk3MS0wLjk2NzggMC4wOTU2LTAuMjg5OCAwLjIyMTEtMC41MzkyIDAuMzc2NC0wLjc0ODNzMC4zNDUtMC4zNzA0IDAuNTY5MS0wLjQ4MzljMC4yMjQtMC4xMTM1IDAuNDg1NC0wLjE3MDMgMC43ODQxLTAuMTcwMyAwLjMwNDcgMCAwLjU3NSAwLjA1OTggMC44MTEgMC4xNzkyIDAuMjM2IDAuMTE2NSAwLjQzNDYgMC4yODM4IDAuNTk1OSAwLjUwMTkgMC4xNjEzIDAuMjE1MSAwLjI4MjMgMC40NzQ5IDAuMzYyOSAwLjc3OTYgMC4wODA3IDAuMzAxNyAwLjEyMSAwLjYzNzggMC4xMjEgMS4wMDgyem0tMS4wNzk4IDAuMDk0MXYtMC4wOTQxYzAtMC4yMjQxLTAuMDIwOS0wLjQzMTctMC4wNjI3LTAuNjIyOC0wLjA0MTktMC4xOTQyLTAuMTA3Ni0wLjM2NDUtMC4xOTcyLTAuNTEwOC0wLjA4OTYtMC4xNDY0LTAuMjA0Ni0wLjI1OTktMC4zNDUtMC4zNDA2LTAuMTM3NC0wLjA4MzYtMC4zMDMyLTAuMTI1NC0wLjQ5NzQtMC4xMjU0LTAuMTkxMSAwLTAuMzU1NCAwLjAzMjgtMC40OTI4IDAuMDk4NS0wLjEzNzUgMC4wNjI4LTAuMjUyNSAwLjE1MDktMC4zNDUxIDAuMjY0NHMtMC4xNjQzIDAuMjQ2NC0wLjIxNSAwLjM5ODhjLTAuMDUwOCAwLjE0OTMtMC4wODY3IDAuMzEyMS0wLjEwNzYgMC40ODg0djAuODY5MmMwLjAzNTkgMC4yMTUxIDAuMDk3MSAwLjQxMjMgMC4xODM3IDAuNTkxNSAwLjA4NjcgMC4xNzkyIDAuMjA5MSAwLjMyMjYgMC4zNjc1IDAuNDMwMSAwLjE2MTMgMC4xMDQ2IDAuMzY3NCAwLjE1NjkgMC42MTgzIDAuMTU2OSAwLjE5NDIgMCAwLjM1OTktMC4wNDE5IDAuNDk3My0wLjEyNTUgMC4xMzc1LTAuMDgzNiAwLjI0OTUtMC4xOTg2IDAuMzM2MS0wLjM0NSAwLjA4OTYtMC4xNDk0IDAuMTU1My0wLjMyMTEgMC4xOTcyLTAuNTE1MyAwLjA0MTgtMC4xOTQxIDAuMDYyNy0wLjQwMDMgMC4wNjI3LTAuNjE4M3ptNC4yNzkgMi40NjQ0Yy0wLjM1ODQgMC0wLjY4MjUtMC4wNTgzLTAuOTcyMy0wLjE3NDgtMC4yODY3LTAuMTE5NS0wLjUzMTctMC4yODUzLTAuNzM0OC0wLjQ5NzMtMC4yMDAxLTAuMjEyMS0wLjM1NC0wLjQ2MTYtMC40NjE1LTAuNzQ4My0wLjEwNzUtMC4yODY4LTAuMTYxMy0wLjU5Ni0wLjE2MTMtMC45Mjc1di0wLjE3OTNjMC0wLjM3OTMgMC4wNTUyLTAuNzIyOCAwLjE2NTgtMS4wMzA1IDAuMTEwNS0wLjMwNzcgMC4yNjQzLTAuNTcwNiAwLjQ2MTUtMC43ODg2IDAuMTk3MS0wLjIyMTEgMC40MzAxLTAuMzg5OCAwLjY5OS0wLjUwNjMgMC4yNjg4LTAuMTE2NSAwLjU2MDEtMC4xNzQ4IDAuODczNy0wLjE3NDggMC4zNDY1IDAgMC42NDk3IDAuMDU4MyAwLjkwOTYgMC4xNzQ4czAuNDc0OSAwLjI4MDggMC42NDUyIDAuNDkyOGMwLjE3MzMgMC4yMDkxIDAuMzAxNyAwLjQ1ODYgMC4zODUzIDAuNzQ4MyAwLjA4NjcgMC4yODk4IDAuMTMgMC42MDk0IDAuMTMgMC45NTg5djAuNDYxNWgtMy43NDU5di0wLjc3NTJoMi42Nzk1di0wLjA4NTFjLTZlLTMgLTAuMTk0Mi0wLjA0NDgtMC4zNzY0LTAuMTE2NS0wLjU0NjYtMC4wNjg3LTAuMTcwMy0wLjE3NDgtMC4zMDc3LTAuMzE4MS0wLjQxMjMtMC4xNDM0LTAuMTA0NS0wLjMzNDYtMC4xNTY4LTAuNTczNi0wLjE1NjgtMC4xNzkyIDAtMC4zMzkgMC4wMzg4LTAuNDc5NCAwLjExNjUtMC4xMzc0IDAuMDc0Ny0wLjI1MjQgMC4xODM3LTAuMzQ1IDAuMzI3MXMtMC4xNjQzIDAuMzE2Ni0wLjIxNTEgMC41MTk4Yy0wLjA0NzggMC4yMDAxLTAuMDcxNyAwLjQyNTYtMC4wNzE3IDAuNjc2NXYwLjE3OTNjMCAwLjIxMjEgMC4wMjg0IDAuNDA5MiAwLjA4NTEgMC41OTE0IDAuMDU5OCAwLjE3OTMgMC4xNDY0IDAuMzM2MSAwLjI1OTkgMC40NzA1czAuMjUwOSAwLjI0MDUgMC40MTIzIDAuMzE4MWMwLjE2MTMgMC4wNzQ3IDAuMzQ1IDAuMTEyIDAuNTUxMSAwLjExMiAwLjI1OTkgMCAwLjQ5MTQtMC4wNTIyIDAuNjk0NS0wLjE1NjggMC4yMDMxLTAuMTA0NSAwLjM3OTQtMC4yNTI0IDAuNTI4Ny0wLjQ0MzZsMC41NjkxIDAuNTUxMmMtMC4xMDQ2IDAuMTUyMy0wLjI0MDUgMC4yOTg3LTAuNDA3OCAwLjQzOTEtMC4xNjczIDAuMTM3NC0wLjM3MTkgMC4yNDk0LTAuNjEzOCAwLjMzNi0wLjIzOSAwLjA4NjYtMC41MTY4IDAuMTMtMC44MzM1IDAuMTN6bTQuMDEwMy00LjAxNDd2My45MjVoLTEuMDc5OXYtNC44NDgxaDEuMDMwNmwwLjA0OTMgMC45MjMxem0xLjQ4MzEtMC45NTQ0LTllLTMgMS4wMDM2Yy0wLjA2NTctMC4wMTE5LTAuMTM3NC0wLjAyMDktMC4yMTUxLTAuMDI2OC0wLjA3NDYtNmUtMyAtMC4xNDkzLTllLTMgLTAuMjI0LTllLTMgLTAuMTg1MiAwLTAuMzQ4IDAuMDI2OS0wLjQ4ODQgMC4wODA3LTAuMTQwNCAwLjA1MDctMC4yNTg0IDAuMTI1NC0wLjM1NCAwLjIyNC0wLjA5MjYgMC4wOTU2LTAuMTY0MyAwLjIxMjEtMC4yMTUgMC4zNDk1LTAuMDUwOCAwLjEzNzQtMC4wODA3IDAuMjkxMi0wLjA4OTYgMC40NjE1bC0wLjI0NjUgMC4wMTc5YzAtMC4zMDQ3IDAuMDI5OS0wLjU4NyAwLjA4OTYtMC44NDY4IDAuMDU5OC0wLjI1OTkgMC4xNDk0LTAuNDg4NCAwLjI2ODktMC42ODU2IDAuMTIyNC0wLjE5NzEgMC4yNzQ4LTAuMzUxIDAuNDU3LTAuNDYxNSAwLjE4NTItMC4xMTA1IDAuMzk4OC0wLjE2NTggMC42NDA3LTAuMTY1OCAwLjA2NTggMCAwLjEzNiA2ZS0zIDAuMjEwNiAwLjAxNzkgMC4wNzc3IDAuMDEyIDAuMTM2IDAuMDI1NCAwLjE3NDggMC4wNDA0em0zLjM5MTkgMy45MDcxdi0yLjMxMmMwLTAuMTczMy0wLjAzMTQtMC4zMjI2LTAuMDk0MS0wLjQ0ODEtMC4wNjI4LTAuMTI1NC0wLjE1ODMtMC4yMjI1LTAuMjg2OC0wLjI5MTItMC4xMjU0LTAuMDY4Ny0wLjI4MzgtMC4xMDMxLTAuNDc0OS0wLjEwMzEtMC4xNzYzIDAtMC4zMjg2IDAuMDI5OS0wLjQ1NzEgMC4wODk2LTAuMTI4NCAwLjA1OTgtMC4yMjg1IDAuMTQwNC0wLjMwMDIgMC4yNDJzLTAuMTA3NSAwLjIxNjYtMC4xMDc1IDAuMzQ1aC0xLjA3NTRjMC0wLjE5MTIgMC4wNDYzLTAuMzc2NCAwLjEzODktMC41NTU2czAuMjI3LTAuMzM5IDAuNDAzMy0wLjQ3OTRjMC4xNzYyLTAuMTQwNCAwLjM4NjgtMC4yNTA5IDAuNjMxOC0wLjMzMTYgMC4yNDQ5LTAuMDgwNyAwLjUxOTctMC4xMjEgMC44MjQ0LTAuMTIxIDAuMzY0NCAwIDAuNjg3IDAuMDYxMyAwLjk2NzggMC4xODM3IDAuMjgzOCAwLjEyMjUgMC41MDY0IDAuMzA3NyAwLjY2NzcgMC41NTU2IDAuMTY0MyAwLjI0NSAwLjI0NjQgMC41NTI3IDAuMjQ2NCAwLjkyMzF2Mi4xNTUyYzAgMC4yMjEgMC4wMTQ5IDAuNDE5NyAwLjA0NDggMC41OTU5IDAuMDMyOSAwLjE3MzMgMC4wNzkyIDAuMzI0MSAwLjEzODkgMC40NTI2djAuMDcxNmgtMS4xMDY3Yy0wLjA1MDgtMC4xMTY0LTAuMDkxMS0wLjI2NDMtMC4xMjEtMC40NDM1LTAuMDI2OS0wLjE4MjMtMC4wNDAzLTAuMzU4NS0wLjA0MDMtMC41Mjg4em0wLjE1NjgtMS45NzYgOWUtMyAwLjY2NzdoLTAuNzc1MmMtMC4yMDAxIDAtMC4zNzY0IDAuMDE5NC0wLjUyODcgMC4wNTgyLTAuMTUyNCAwLjAzNTktMC4yNzkzIDAuMDg5Ni0wLjM4MDkgMC4xNjEzLTAuMTAxNSAwLjA3MTctMC4xNzc3IDAuMTU4My0wLjIyODUgMC4yNTk5cy0wLjA3NjIgMC4yMTY2LTAuMDc2MiAwLjM0NWMwIDAuMTI4NSAwLjAyOTkgMC4yNDY1IDAuMDg5NiAwLjM1NCAwLjA1OTggMC4xMDQ1IDAuMTQ2NCAwLjE4NjcgMC4yNTk5IDAuMjQ2NCAwLjExNjUgMC4wNTk4IDAuMjU2OSAwLjA4OTYgMC40MjEyIDAuMDg5NiAwLjIyMTEgMCAwLjQxMzctMC4wNDQ4IDAuNTc4LTAuMTM0NCAwLjE2NzMtMC4wOTI2IDAuMjk4Ny0wLjIwNDYgMC4zOTQzLTAuMzM2IDAuMDk1Ni0wLjEzNDQgMC4xNDY0LTAuMjYxNCAwLjE1MjQtMC4zODA5bDAuMzQ5NSAwLjQ3OTVjLTAuMDM1OSAwLjEyMjQtMC4wOTcxIDAuMjUzOS0wLjE4MzggMC4zOTQzLTAuMDg2NiAwLjE0MDMtMC4yMDAxIDAuMjc0OC0wLjM0MDUgMC40MDMyLTAuMTM3NCAwLjEyNTUtMC4zMDMyIDAuMjI4NS0wLjQ5NzMgMC4zMDkyLTAuMTkxMiAwLjA4MDYtMC40MTIzIDAuMTIxLTAuNjYzMiAwLjEyMS0wLjMxNjYgMC0wLjU5ODktMC4wNjI4LTAuODQ2OC0wLjE4ODItMC4yNDgtMC4xMjg1LTAuNDQyMS0wLjMwMDItMC41ODI1LTAuNTE1My0wLjE0MDQtMC4yMTgxLTAuMjEwNi0wLjQ2NDUtMC4yMTA2LTAuNzM5MyAwLTAuMjU2OSAwLjA0NzgtMC40ODM5IDAuMTQzNC0wLjY4MTEgMC4wOTg1LTAuMjAwMSAwLjI0MTktMC4zNjc0IDAuNDMwMS0wLjUwMTggMC4xOTEyLTAuMTM0NCAwLjQyNDItMC4yMzYgMC42OTktMC4zMDQ3IDAuMjc0OC0wLjA3MTcgMC41ODg1LTAuMTA3NiAwLjk0MDktMC4xMDc2aDAuODQ2OXptNC40MjI0LTEuODk5OHYwLjc4ODZoLTIuNzMzMnYtMC43ODg2aDIuNzMzMnptLTEuOTQ0Ni0xLjE4NzRoMS4wNzk5djQuNjk1OGMwIDAuMTQ5NCAwLjAyMDkgMC4yNjQ0IDAuMDYyNyAwLjM0NSAwLjA0NDggMC4wNzc3IDAuMTA2IDAuMTMgMC4xODM3IDAuMTU2OSAwLjA3NzcgMC4wMjY4IDAuMTY4OCAwLjA0MDMgMC4yNzMzIDAuMDQwMyAwLjA3NDcgMCAwLjE0NjQtMC4wMDQ1IDAuMjE1MS0wLjAxMzUgMC4wNjg3LTAuMDA4OSAwLjEyNC0wLjAxNzkgMC4xNjU4LTAuMDI2OGwwLjAwNDUgMC44MjQ0Yy0wLjA4OTYgMC4wMjY5LTAuMTk0MiAwLjA1MDgtMC4zMTM3IDAuMDcxNy0wLjExNjUgMC4wMjA5LTAuMjUwOSAwLjAzMTQtMC40MDMyIDAuMDMxNC0wLjI0OCAwLTAuNDY3NS0wLjA0MzQtMC42NTg3LTAuMTMtMC4xOTEyLTAuMDg5Ni0wLjM0MDUtMC4yMzQ1LTAuNDQ4MS0wLjQzNDYtMC4xMDc1LTAuMjAwMS0wLjE2MTMtMC40NjYtMC4xNjEzLTAuNzk3NnYtNC43NjN6bTUuODM4NCA0Ljg5M3YtMy43MDU2aDEuMDg0M3Y0Ljg0ODFoLTEuMDIxNmwtMC4wNjI3LTEuMTQyNXptMC4xNTIzLTEuMDA4MiAwLjM2My0wLjAwODljMCAwLjMyNTUtMC4wMzU5IDAuNjI1OC0wLjEwNzYgMC45MDA2LTAuMDcxNyAwLjI3MTgtMC4xODIyIDAuNTA5My0wLjMzMTYgMC43MTI0LTAuMTQ5MyAwLjIwMDEtMC4zNDA1IDAuMzU3LTAuNTczNSAwLjQ3MDUtMC4yMzMgMC4xMTA1LTAuNTEyMyAwLjE2NTgtMC44Mzc5IDAuMTY1OC0wLjIzNiAwLTAuNDUyNS0wLjAzNDQtMC42NDk3LTAuMTAzMS0wLjE5NzEtMC4wNjg3LTAuMzY3NC0wLjE3NDctMC41MTA4LTAuMzE4MS0wLjE0MDQtMC4xNDM0LTAuMjQ5NC0wLjMzMDEtMC4zMjcxLTAuNTYwMS0wLjA3NzYtMC4yMy0wLjExNjUtMC41MDQ4LTAuMTE2NS0wLjgyNDV2LTMuMTMyaDEuMDc5OXYzLjE0MWMwIDAuMTc2MiAwLjAyMDkgMC4zMjQxIDAuMDYyNyAwLjQ0MzYgMC4wNDE4IDAuMTE2NSAwLjA5ODYgMC4yMTA2IDAuMTcwMyAwLjI4MjNzMC4xNTUzIDAuMTIyNCAwLjI1MDkgMC4xNTIzIDAuMTk3MSAwLjA0NDggMC4zMDQ3IDAuMDQ0OGMwLjMwNzcgMCAwLjU0OTYtMC4wNTk3IDAuNzI1OS0wLjE3OTIgMC4xNzkyLTAuMTIyNSAwLjMwNjEtMC4yODY4IDAuMzgwOC0wLjQ5MjkgMC4wNzc3LTAuMjA2MSAwLjExNjUtMC40Mzc2IDAuMTE2NS0wLjY5NDV6bTMuMjY2NC0xLjc3NDN2My45MjVoLTEuMDc5OHYtNC44NDgxaDEuMDMwNmwwLjA0OTIgMC45MjMxem0xLjQ4MzItMC45NTQ0LTllLTMgMS4wMDM2Yy0wLjA2NTctMC4wMTE5LTAuMTM3NC0wLjAyMDktMC4yMTUxLTAuMDI2OC0wLjA3NDctNmUtMyAtMC4xNDkzLTllLTMgLTAuMjI0LTllLTMgLTAuMTg1MiAwLTAuMzQ4IDAuMDI2OS0wLjQ4ODQgMC4wODA3LTAuMTQwNCAwLjA1MDctMC4yNTg0IDAuMTI1NC0wLjM1NCAwLjIyNC0wLjA5MjYgMC4wOTU2LTAuMTY0MyAwLjIxMjEtMC4yMTUxIDAuMzQ5NS0wLjA1MDcgMC4xMzc0LTAuMDgwNiAwLjI5MTItMC4wODk2IDAuNDYxNWwtMC4yNDY0IDAuMDE3OWMwLTAuMzA0NyAwLjAyOTktMC41ODcgMC4wODk2LTAuODQ2OCAwLjA1OTctMC4yNTk5IDAuMTQ5NC0wLjQ4ODQgMC4yNjg4LTAuNjg1NiAwLjEyMjUtMC4xOTcxIDAuMjc0OS0wLjM1MSAwLjQ1NzEtMC40NjE1IDAuMTg1Mi0wLjExMDUgMC4zOTg4LTAuMTY1OCAwLjY0MDctMC4xNjU4IDAuMDY1NyAwIDAuMTM1OSA2ZS0zIDAuMjEwNiAwLjAxNzkgMC4wNzc3IDAuMDEyIDAuMTM1OSAwLjAyNTQgMC4xNzQ4IDAuMDQwNHptMi44Njc2IDQuOTY5MWMtMC4zNTg1IDAtMC42ODI2LTAuMDU4My0wLjk3MjMtMC4xNzQ4LTAuMjg2OC0wLjExOTUtMC41MzE3LTAuMjg1My0wLjczNDgtMC40OTczLTAuMjAwMi0wLjIxMjEtMC4zNTQtMC40NjE2LTAuNDYxNi0wLjc0ODMtMC4xMDc1LTAuMjg2OC0wLjE2MTMtMC41OTYtMC4xNjEzLTAuOTI3NXYtMC4xNzkzYzAtMC4zNzkzIDAuMDU1My0wLjcyMjggMC4xNjU4LTEuMDMwNSAwLjExMDYtMC4zMDc3IDAuMjY0NC0wLjU3MDYgMC40NjE1LTAuNzg4NiAwLjE5NzItMC4yMjExIDAuNDMwMi0wLjM4OTggMC42OTktMC41MDYzIDAuMjY4OS0wLjExNjUgMC41NjAxLTAuMTc0OCAwLjg3MzgtMC4xNzQ4IDAuMzQ2NSAwIDAuNjQ5NyAwLjA1ODMgMC45MDk1IDAuMTc0OCAwLjI1OTkgMC4xMTY1IDAuNDc1IDAuMjgwOCAwLjY0NTMgMC40OTI4IDAuMTcyOSAwLjIwOTEgMC4zMDE5IDAuNDU4NiAwLjM4NDkgMC43NDgzIDAuMDg3IDAuMjg5OCAwLjEzIDAuNjA5NCAwLjEzIDAuOTU4OXYwLjQ2MTVoLTMuNzQ1NXYtMC43NzUyaDIuNjc5NHYtMC4wODUxYy0wLjAwNTktMC4xOTQyLTAuMDQ0OC0wLjM3NjQtMC4xMTY1LTAuNTQ2Ni0wLjA2ODctMC4xNzAzLTAuMTc0Ny0wLjMwNzctMC4zMTgxLTAuNDEyMy0wLjE0MzQtMC4xMDQ1LTAuMzM0NS0wLjE1NjgtMC41NzM1LTAuMTU2OC0wLjE3OTIgMC0wLjMzOTEgMC4wMzg4LTAuNDc5NSAwLjExNjUtMC4xMzc0IDAuMDc0Ny0wLjI1MjQgMC4xODM3LTAuMzQ1IDAuMzI3MXMtMC4xNjQzIDAuMzE2Ni0wLjIxNSAwLjUxOThjLTAuMDQ3OCAwLjIwMDEtMC4wNzE3IDAuNDI1Ni0wLjA3MTcgMC42NzY1djAuMTc5M2MwIDAuMjEyMSAwLjAyODMgMC40MDkyIDAuMDg1MSAwLjU5MTQgMC4wNTk3IDAuMTc5MyAwLjE0NjQgMC4zMzYxIDAuMjU5OSAwLjQ3MDVzMC4yNTA5IDAuMjQwNSAwLjQxMjIgMC4zMTgxYzAuMTYxMyAwLjA3NDcgMC4zNDUgMC4xMTIgMC41NTExIDAuMTEyIDAuMjU5OSAwIDAuNDkxNC0wLjA1MjIgMC42OTQ1LTAuMTU2OCAwLjIwMzItMC4xMDQ1IDAuMzc5NC0wLjI1MjQgMC41Mjg4LTAuNDQzNmwwLjU2ODggMC41NTEyYy0wLjEwNCAwLjE1MjMtMC4yNCAwLjI5ODctMC40MDc1IDAuNDM5MS0wLjE2NzMgMC4xMzc0LTAuMzcxOSAwLjI0OTQtMC42MTM5IDAuMzM2LTAuMjM5IDAuMDg2Ni0wLjUxNjggMC4xMy0wLjgzMzQgMC4xM3oiIGZpbGwtb3BhY2l0eT0iLjg3Ii8+CiAgIDxwYXRoIGQ9Im01MC4zNTYgMzYuNTk2djAuNjY4N2gtMi40NTY2di0wLjY2ODdoMi40NTY2em0tMi4yMjEzLTQuMjI0MnY0Ljg5MjloLTAuODQzNXYtNC44OTI5aDAuODQzNXptNC45ODU5IDQuMTYzN3YtMS43MzRjMC0wLjEzLTAuMDIzNi0wLjI0Mi0wLjA3MDYtMC4zMzYxLTAuMDQ3MS0wLjA5NDEtMC4xMTg3LTAuMTY2OS0wLjIxNTEtMC4yMTg0LTAuMDk0MS0wLjA1MTUtMC4yMTI4LTAuMDc3My0wLjM1NjItMC4wNzczLTAuMTMyMiAwLTAuMjQ2NCAwLjAyMjQtMC4zNDI4IDAuMDY3Mi0wLjA5NjMgMC4wNDQ4LTAuMTcxNCAwLjEwNTMtMC4yMjUxIDAuMTgxNS0wLjA1MzggMC4wNzYyLTAuMDgwNyAwLjE2MjQtMC4wODA3IDAuMjU4N2gtMC44MDY1YzAtMC4xNDMzIDAuMDM0Ny0wLjI4MjIgMC4xMDQyLTAuNDE2NyAwLjA2OTQtMC4xMzQ0IDAuMTcwMi0wLjI1NDIgMC4zMDI0LTAuMzU5NXMwLjI5MDEtMC4xODgyIDAuNDczOS0wLjI0ODdjMC4xODM3LTAuMDYwNSAwLjM4OTgtMC4wOTA3IDAuNjE4My0wLjA5MDcgMC4yNzMzIDAgMC41MTUzIDAuMDQ1OSAwLjcyNTkgMC4xMzc3IDAuMjEyOCAwLjA5MTkgMC4zNzk3IDAuMjMwOCAwLjUwMDcgMC40MTY3IDAuMTIzMiAwLjE4MzcgMC4xODQ4IDAuNDE0NSAwLjE4NDggMC42OTIzdjEuNjE2NGMwIDAuMTY1OCAwLjAxMTIgMC4zMTQ4IDAuMDMzNiAwLjQ0NyAwLjAyNDcgMC4xMjk5IDAuMDU5NCAwLjI0MyAwLjEwNDIgMC4zMzk0djAuMDUzN2gtMC44MzAxYy0wLjAzOC0wLjA4NzMtMC4wNjgzLTAuMTk4Mi0wLjA5MDctMC4zMzI2LTAuMDIwMi0wLjEzNjctMC4wMzAyLTAuMjY4OS0wLjAzMDItMC4zOTY2em0wLjExNzYtMS40ODIgMC4wMDY3IDAuNTAwN2gtMC41ODE0Yy0wLjE1MDEgMC0wLjI4MjMgMC4wMTQ2LTAuMzk2NSAwLjA0MzctMC4xMTQzIDAuMDI2OS0wLjIwOTUgMC4wNjcyLTAuMjg1NyAwLjEyMS0wLjA3NjEgMC4wNTM4LTAuMTMzMyAwLjExODctMC4xNzEzIDAuMTk0OS0wLjAzODEgMC4wNzYyLTAuMDU3MiAwLjE2MjQtMC4wNTcyIDAuMjU4OCAwIDAuMDk2MyAwLjAyMjQgMC4xODQ4IDAuMDY3MiAwLjI2NTUgMC4wNDQ4IDAuMDc4NCAwLjEwOTggMC4xNCAwLjE5NDkgMC4xODQ4IDAuMDg3NCAwLjA0NDggMC4xOTI3IDAuMDY3MiAwLjMxNTkgMC4wNjcyIDAuMTY1OCAwIDAuMzEwMy0wLjAzMzYgMC40MzM1LTAuMTAwOCAwLjEyNTUtMC4wNjk1IDAuMjI0MS0wLjE1MzUgMC4yOTU4LTAuMjUyMSAwLjA3MTctMC4xMDA4IDAuMTA5Ny0wLjE5NiAwLjExNDItMC4yODU2bDAuMjYyMiAwLjM1OTZjLTAuMDI2OSAwLjA5MTgtMC4wNzI5IDAuMTkwNC0wLjEzNzggMC4yOTU3LTAuMDY1IDAuMTA1My0wLjE1MDEgMC4yMDYxLTAuMjU1NCAwLjMwMjQtMC4xMDMxIDAuMDk0MS0wLjIyNzQgMC4xNzE0LTAuMzczIDAuMjMxOS0wLjE0MzQgMC4wNjA1LTAuMzA5MiAwLjA5MDgtMC40OTc0IDAuMDkwOC0wLjIzNzUgMC0wLjQ0OTItMC4wNDcxLTAuNjM1MS0wLjE0MTItMC4xODYtMC4wOTYzLTAuMzMxNi0wLjIyNTEtMC40MzY5LTAuMzg2NC0wLjEwNTMtMC4xNjM2LTAuMTU4LTAuMzQ4NC0wLjE1OC0wLjU1NDUgMC0wLjE5MjcgMC4wMzU5LTAuMzYzIDAuMTA3Ni0wLjUxMDggMC4wNzM5LTAuMTUwMSAwLjE4MTQtMC4yNzU2IDAuMzIyNi0wLjM3NjQgMC4xNDM0LTAuMTAwOCAwLjMxODEtMC4xNzcgMC41MjQyLTAuMjI4NSAwLjIwNjEtMC4wNTM4IDAuNDQxNC0wLjA4MDcgMC43MDU3LTAuMDgwN2gwLjYzNTJ6bTMuNzI1NyAxLjIyNjZjMC0wLjA4MDYtMC4wMjAyLTAuMTUzNC0wLjA2MDUtMC4yMTg0LTAuMDQwMy0wLjA2NzItMC4xMTc2LTAuMTI3Ny0wLjIzMTktMC4xODE1LTAuMTEyLTAuMDUzOC0wLjI3NzgtMC4xMDMtMC40OTczLTAuMTQ3OS0wLjE5MjctMC4wNDI1LTAuMzY5Ny0wLjA5MjktMC41MzEtMC4xNTEyLTAuMTU5MS0wLjA2MDUtMC4yOTU3LTAuMTMzMy0wLjQxLTAuMjE4NC0wLjExNDItMC4wODUxLTAuMjAyNy0wLjE4Ni0wLjI2NTUtMC4zMDI1LTAuMDYyNy0wLjExNjUtMC4wOTQxLTAuMjUwOS0wLjA5NDEtMC40MDMyIDAtMC4xNDc5IDAuMDMyNS0wLjI4NzkgMC4wOTc1LTAuNDIwMXMwLjE1NzktMC4yNDg3IDAuMjc4OS0wLjM0OTUgMC4yNjc3LTAuMTgwMyAwLjQ0MDItMC4yMzg2YzAuMTc0OC0wLjA1ODIgMC4zNjk3LTAuMDg3MyAwLjU4NDgtMC4wODczIDAuMzA0NyAwIDAuNTY1NyAwLjA1MTUgMC43ODMgMC4xNTQ1IDAuMjE5NSAwLjEwMDkgMC4zODc2IDAuMjM4NiAwLjUwNDEgMC40MTM0IDAuMTE2NSAwLjE3MjUgMC4xNzQ3IDAuMzY3NCAwLjE3NDcgMC41ODQ3aC0wLjgwOTljMC0wLjA5NjMtMC4wMjQ2LTAuMTg1OS0wLjA3MzktMC4yNjg4LTAuMDQ3MS0wLjA4NTItMC4xMTg4LTAuMTUzNS0wLjIxNTEtMC4yMDUtMC4wOTYzLTAuMDUzOC0wLjIxNzMtMC4wODA3LTAuMzYyOS0wLjA4MDctMC4xMzg5IDAtMC4yNTQzIDAuMDIyNC0wLjM0NjIgMC4wNjcyLTAuMDg5NiAwLjA0MjYtMC4xNTY4IDAuMDk4Ni0wLjIwMTYgMC4xNjgxLTAuMDQyNiAwLjA2OTQtMC4wNjM4IDAuMTQ1Ni0wLjA2MzggMC4yMjg1IDAgMC4wNjA1IDAuMDExMiAwLjExNTQgMC4wMzM2IDAuMTY0NiAwLjAyNDYgMC4wNDcxIDAuMDY0OSAwLjA5MDggMC4xMjA5IDAuMTMxMSAwLjA1NjEgMC4wMzgxIDAuMTMyMiAwLjA3MzkgMC4yMjg2IDAuMTA3NSAwLjA5ODUgMC4wMzM2IDAuMjIxOCAwLjA2NjEgMC4zNjk2IDAuMDk3NSAwLjI3NzggMC4wNTgyIDAuNTE2NCAwLjEzMzMgMC43MTU4IDAuMjI1MSAwLjIwMTYgMC4wODk3IDAuMzU2MiAwLjIwNjIgMC40NjM4IDAuMzQ5NSAwLjEwNzUgMC4xNDEyIDAuMTYxMyAwLjMyMDQgMC4xNjEzIDAuNTM3NyAwIDAuMTYxMy0wLjAzNDggMC4zMDkyLTAuMTA0MiAwLjQ0MzYtMC4wNjcyIDAuMTMyMi0wLjE2NTggMC4yNDc2LTAuMjk1NyAwLjM0NjItMC4xMyAwLjA5NjMtMC4yODU3IDAuMTcxMy0wLjQ2NzIgMC4yMjUxLTAuMTc5MiAwLjA1MzgtMC4zODA4IDAuMDgwNy0wLjYwNDggMC4wODA3LTAuMzI5NCAwLTAuNjA4My0wLjA1ODMtMC44MzY4LTAuMTc0OC0wLjIyODUtMC4xMTg3LTAuNDAyMi0wLjI3LTAuNTIwOS0wLjQ1MzctMC4xMTY1LTAuMTg1OS0wLjE3NDctMC4zNzg2LTAuMTc0Ny0wLjU3OGgwLjc4M2MwLjAwODkgMC4xNTAxIDAuMDUwNCAwLjI3IDAuMTI0MyAwLjM1OTYgMC4wNzYyIDAuMDg3NCAwLjE3MDMgMC4xNTEyIDAuMjgyMyAwLjE5MTYgMC4xMTQyIDAuMDM4IDAuMjMxOSAwLjA1NzEgMC4zNTI4IDAuMDU3MSAwLjE0NTcgMCAwLjI2NzgtMC4wMTkxIDAuMzY2My0wLjA1NzEgMC4wOTg2LTAuMDQwNCAwLjE3MzctMC4wOTQxIDAuMjI1Mi0wLjE2MTMgMC4wNTE1LTAuMDY5NSAwLjA3NzMtMC4xNDc5IDAuMDc3My0wLjIzNTN6bTMuMzEyMy0yLjY1MTR2MC41OTE0aC0yLjA0OTl2LTAuNTkxNGgyLjA0OTl6bS0xLjQ1ODQtMC44OTA2aDAuODA5OXYzLjUyMTljMCAwLjExMiAwLjAxNTYgMC4xOTgyIDAuMDQ3IDAuMjU4NyAwLjAzMzYgMC4wNTgzIDAuMDc5NSAwLjA5NzUgMC4xMzc4IDAuMTE3NiAwLjA1ODIgMC4wMjAyIDAuMTI2NiAwLjAzMDMgMC4yMDUgMC4wMzAzIDAuMDU2IDAgMC4xMDk4LTAuMDAzNCAwLjE2MTMtMC4wMTAxczAuMDkzLTAuMDEzNCAwLjEyNDMtMC4wMjAybDAuMDAzNCAwLjYxODRjLTAuMDY3MiAwLjAyMDEtMC4xNDU2IDAuMDM4MS0wLjIzNTMgMC4wNTM3LTAuMDg3MyAwLjAxNTctMC4xODgxIDAuMDIzNi0wLjMwMjQgMC4wMjM2LTAuMTg2IDAtMC4zNTA2LTAuMDMyNS0wLjQ5NC0wLjA5NzUtMC4xNDM0LTAuMDY3Mi0wLjI1NTQtMC4xNzU5LTAuMzM2MS0wLjMyNi0wLjA4MDYtMC4xNTAxLTAuMTIwOS0wLjM0OTUtMC4xMjA5LTAuNTk4MXYtMy41NzIzem02LjI3MTggMy42Njk3di0yLjc3OTFoMC44MTMzdjMuNjM2aC0wLjc2NjJsLTAuMDQ3MS0wLjg1Njl6bTAuMTE0My0wLjc1NjEgMC4yNzIyLTAuMDA2N2MwIDAuMjQ0Mi0wLjAyNjkgMC40NjkzLTAuMDgwNyAwLjY3NTQtMC4wNTM3IDAuMjAzOS0wLjEzNjYgMC4zODItMC4yNDg2IDAuNTM0NC0wLjExMjEgMC4xNTAxLTAuMjU1NCAwLjI2NzctMC40MzAyIDAuMzUyOC0wLjE3NDcgMC4wODI5LTAuMzg0MiAwLjEyNDQtMC42Mjg0IDAuMTI0NC0wLjE3NyAwLTAuMzM5NC0wLjAyNTgtMC40ODczLTAuMDc3My0wLjE0NzgtMC4wNTE2LTAuMjc1NS0wLjEzMTEtMC4zODMxLTAuMjM4Ni0wLjEwNTMtMC4xMDc2LTAuMTg3MS0wLjI0NzYtMC4yNDUzLTAuNDIwMS0wLjA1ODMtMC4xNzI1LTAuMDg3NC0wLjM3ODYtMC4wODc0LTAuNjE4M3YtMi4zNDloMC44MDk5djIuMzU1N2MwIDAuMTMyMiAwLjAxNTcgMC4yNDMxIDAuMDQ3MSAwLjMzMjcgMC4wMzEzIDAuMDg3NCAwLjA3MzkgMC4xNTc5IDAuMTI3NyAwLjIxMTcgMC4wNTM3IDAuMDUzOCAwLjExNjUgMC4wOTE4IDAuMTg4MSAwLjExNDMgMC4wNzE3IDAuMDIyNCAwLjE0NzkgMC4wMzM2IDAuMjI4NiAwLjAzMzYgMC4yMzA3IDAgMC40MTIyLTAuMDQ0OSAwLjU0NDQtMC4xMzQ1IDAuMTM0NC0wLjA5MTggMC4yMjk2LTAuMjE1IDAuMjg1Ni0wLjM2OTYgMC4wNTgzLTAuMTU0NiAwLjA4NzQtMC4zMjgyIDAuMDg3NC0wLjUyMDl6bTIuNDg1Ny0xLjMyNHY0LjMzNWgtMC44MDk5di01LjAzNGgwLjc0NmwwLjA2MzkgMC42OTl6bTIuMzY5MSAxLjA4NTR2MC4wNzA2YzAgMC4yNjQzLTAuMDMxMyAwLjUwOTctMC4wOTQxIDAuNzM1OS0wLjA2MDUgMC4yMjQxLTAuMTUxMiAwLjQyMDEtMC4yNzIyIDAuNTg4MS0wLjExODcgMC4xNjU4LTAuMjY1NSAwLjI5NDYtMC40NDAyIDAuMzg2NS0wLjE3NDggMC4wOTE4LTAuMzc2NCAwLjEzNzgtMC42MDQ5IDAuMTM3OC0wLjIyNjMgMC0wLjQyNDUtMC4wNDE1LTAuNTk0OC0wLjEyNDQtMC4xNjgtMC4wODUxLTAuMzEwMy0wLjIwNS0wLjQyNjgtMC4zNTk2LTAuMTE2NS0wLjE1NDUtMC4yMTA2LTAuMzM2LTAuMjgyMy0wLjU0NDQtMC4wNjk0LTAuMjEwNi0wLjExODctMC40NDEzLTAuMTQ3OC0wLjY5MjJ2LTAuMjcyMmMwLjAyOTEtMC4yNjY2IDAuMDc4NC0wLjUwODYgMC4xNDc4LTAuNzI1OSAwLjA3MTctMC4yMTczIDAuMTY1OC0wLjQwNDQgMC4yODIzLTAuNTYxMnMwLjI1ODgtMC4yNzc4IDAuNDI2OC0wLjM2MjljMC4xNjgtMC4wODUyIDAuMzY0LTAuMTI3NyAwLjU4ODEtMC4xMjc3IDAuMjI4NSAwIDAuNDMxMiAwLjA0NDggMC42MDgyIDAuMTM0NCAwLjE3NyAwLjA4NzMgMC4zMjYgMC4yMTI4IDAuNDQ3IDAuMzc2NCAwLjEyMSAwLjE2MTMgMC4yMTE3IDAuMzU2MiAwLjI3MjIgMC41ODQ3IDAuMDYwNSAwLjIyNjMgMC4wOTA3IDAuNDc4MyAwLjA5MDcgMC43NTYxem0tMC44MDk5IDAuMDcwNnYtMC4wNzA2YzAtMC4xNjgtMC4wMTU2LTAuMzIzNy0wLjA0Ny0wLjQ2NzEtMC4wMzE0LTAuMTQ1Ni0wLjA4MDctMC4yNzMzLTAuMTQ3OS0wLjM4MzFzLTAuMTUzNC0wLjE5NDktMC4yNTg3LTAuMjU1NGMtMC4xMDMxLTAuMDYyNy0wLjIyNzQtMC4wOTQxLTAuMzczMS0wLjA5NDEtMC4xNDMzIDAtMC4yNjY2IDAuMDI0Ni0wLjM2OTYgMC4wNzM5LTAuMTAzMSAwLjA0NzEtMC4xODkzIDAuMTEzMi0wLjI1ODggMC4xOTgzLTAuMDY5NCAwLjA4NTEtMC4xMjMyIDAuMTg0OC0wLjE2MTMgMC4yOTkxLTAuMDM4MSAwLjExMi0wLjA2NDkgMC4yMzQxLTAuMDgwNiAwLjM2NjN2MC42NTE5YzAuMDI2OSAwLjE2MTMgMC4wNzI4IDAuMzA5MiAwLjEzNzggMC40NDM2IDAuMDY0OSAwLjEzNDQgMC4xNTY4IDAuMjQyIDAuMjc1NSAwLjMyMjYgMC4xMjEgMC4wNzg0IDAuMjc1NiAwLjExNzYgMC40NjM4IDAuMTE3NiAwLjE0NTYgMCAwLjI2OTktMC4wMzEzIDAuMzczLTAuMDk0MSAwLjEwMy0wLjA2MjcgMC4xODcxLTAuMTQ4OSAwLjI1Mi0wLjI1ODcgMC4wNjcyLTAuMTEyIDAuMTE2NS0wLjI0MDkgMC4xNDc5LTAuMzg2NXMwLjA0Ny0wLjMwMDIgMC4wNDctMC40NjM3em0zLjg2MDIgMS4wMjgzdi00LjQwOWgwLjgxMzJ2NS4xNjE3aC0wLjczNTlsLTAuMDc3My0wLjc1Mjd6bS0yLjM2NTktMS4wMjV2LTAuMDcwNWMwLTAuMjc1NiAwLjAzMjUtMC41MjY1IDAuMDk3NS0wLjc1MjggMC4wNjUtMC4yMjg1IDAuMTU5MS0wLjQyNDUgMC4yODIzLTAuNTg4MSAwLjEyMzItMC4xNjU4IDAuMjczMy0wLjI5MjQgMC40NTAzLTAuMzc5NyAwLjE3Ny0wLjA4OTYgMC4zNzY0LTAuMTM0NCAwLjU5ODItMC4xMzQ0IDAuMjE5NSAwIDAuNDEyMiAwLjA0MjUgMC41NzggMC4xMjc3IDAuMTY1OCAwLjA4NTEgMC4zMDY5IDAuMjA3MiAwLjQyMzQgMC4zNjYyIDAuMTE2NSAwLjE1NjkgMC4yMDk1IDAuMzQ1MSAwLjI3ODkgMC41NjQ2IDAuMDY5NSAwLjIxNzMgMC4xMTg4IDAuNDU5MyAwLjE0NzkgMC43MjU5djAuMjI1MWMtMC4wMjkxIDAuMjU5OS0wLjA3ODQgMC40OTc0LTAuMTQ3OSAwLjcxMjUtMC4wNjk0IDAuMjE1LTAuMTYyNCAwLjQwMS0wLjI3ODkgMC41NTc4cy0wLjI1ODggMC4yNzc4LTAuNDI2OCAwLjM2M2MtMC4xNjU4IDAuMDg1MS0wLjM1OTYgMC4xMjc3LTAuNTgxMyAwLjEyNzctMC4yMTk2IDAtMC40MTc5LTAuMDQ2LTAuNTk0OS0wLjEzNzgtMC4xNzQ3LTAuMDkxOS0wLjMyMzctMC4yMjA3LTAuNDQ2OS0wLjM4NjVzLTAuMjE3My0wLjM2MDctMC4yODIzLTAuNTg0N2MtMC4wNjUtMC4yMjYzLTAuMDk3NS0wLjQ3MTYtMC4wOTc1LTAuNzM2em0wLjgwOTktMC4wNzA1djAuMDcwNWMwIDAuMTY1OCAwLjAxNDYgMC4zMjA0IDAuMDQzNyAwLjQ2MzggMC4wMzE0IDAuMTQzNCAwLjA3OTYgMC4yNjk5IDAuMTQ0NSAwLjM3OTcgMC4wNjUgMC4xMDc2IDAuMTQ5IDAuMTkyNyAwLjI1MjEgMC4yNTU0IDAuMTA1MyAwLjA2MDUgMC4yMzA3IDAuMDkwOCAwLjM3NjMgMC4wOTA4IDAuMTgzOCAwIDAuMzM1LTAuMDQwNCAwLjQ1MzctMC4xMjEgMC4xMTg4LTAuMDgwNyAwLjIxMTctMC4xODkzIDAuMjc4OS0wLjMyNiAwLjA2OTUtMC4xMzg5IDAuMTE2NS0wLjI5MzUgMC4xNDEyLTAuNDYzN3YtMC42MDgzYy0wLjAxMzUtMC4xMzIyLTAuMDQxNS0wLjI1NTQtMC4wODQtMC4zNjk3LTAuMDQwNC0wLjExNDItMC4wOTUyLTAuMjEzOS0wLjE2NDctMC4yOTktMC4wNjk1LTAuMDg3NC0wLjE1NTctMC4xNTQ2LTAuMjU4OC0wLjIwMTctMC4xMDA4LTAuMDQ5My0wLjIyMDYtMC4wNzM5LTAuMzU5NS0wLjA3MzktMC4xNDc5IDAtMC4yNzM0IDAuMDMxNC0wLjM3NjQgMC4wOTQxLTAuMTAzMSAwLjA2MjctMC4xODgyIDAuMTQ5LTAuMjU1NCAwLjI1ODctMC4wNjUgMC4xMDk4LTAuMTEzMiAwLjIzNzUtMC4xNDQ1IDAuMzgzMS0wLjAzMTQgMC4xNDU3LTAuMDQ3MSAwLjMwMTQtMC4wNDcxIDAuNDY3MnptNS40MDk0IDEuMTE5di0xLjczNGMwLTAuMTMtMC4wMjM2LTAuMjQyLTAuMDcwNi0wLjMzNjEtMC4wNDcxLTAuMDk0MS0wLjExODgtMC4xNjY5LTAuMjE1MS0wLjIxODQtMC4wOTQxLTAuMDUxNS0wLjIxMjgtMC4wNzczLTAuMzU2Mi0wLjA3NzMtMC4xMzIyIDAtMC4yNDY0IDAuMDIyNC0wLjM0MjggMC4wNjcyLTAuMDk2MyAwLjA0NDgtMC4xNzE0IDAuMTA1My0wLjIyNTEgMC4xODE1LTAuMDUzOCAwLjA3NjItMC4wODA3IDAuMTYyNC0wLjA4MDcgMC4yNTg3aC0wLjgwNjVjMC0wLjE0MzMgMC4wMzQ3LTAuMjgyMiAwLjEwNDItMC40MTY3IDAuMDY5NC0wLjEzNDQgMC4xNzAyLTAuMjU0MiAwLjMwMjQtMC4zNTk1czAuMjkwMS0wLjE4ODIgMC40NzM4LTAuMjQ4N2MwLjE4MzgtMC4wNjA1IDAuMzg5OS0wLjA5MDcgMC42MTg0LTAuMDkwNyAwLjI3MzMgMCAwLjUxNTMgMC4wNDU5IDAuNzI1OSAwLjEzNzcgMC4yMTI4IDAuMDkxOSAwLjM3OTcgMC4yMzA4IDAuNTAwNyAwLjQxNjcgMC4xMjMyIDAuMTgzNyAwLjE4NDggMC40MTQ1IDAuMTg0OCAwLjY5MjN2MS42MTY0YzAgMC4xNjU4IDAuMDExMiAwLjMxNDggMC4wMzM2IDAuNDQ3IDAuMDI0NyAwLjEyOTkgMC4wNTk0IDAuMjQzIDAuMTA0MiAwLjMzOTR2MC4wNTM3aC0wLjgzMDFjLTAuMDM4LTAuMDg3My0wLjA2ODMtMC4xOTgyLTAuMDkwNy0wLjMzMjYtMC4wMjAyLTAuMTM2Ny0wLjAzMDItMC4yNjg5LTAuMDMwMi0wLjM5NjZ6bTAuMTE3Ni0xLjQ4MiAwLjAwNjcgMC41MDA3aC0wLjU4MTRjLTAuMTUwMSAwLTAuMjgyMyAwLjAxNDYtMC4zOTY1IDAuMDQzNy0wLjExNDMgMC4wMjY5LTAuMjA5NSAwLjA2NzItMC4yODU3IDAuMTIxLTAuMDc2MSAwLjA1MzgtMC4xMzMzIDAuMTE4Ny0wLjE3MTMgMC4xOTQ5LTAuMDM4MSAwLjA3NjItMC4wNTcyIDAuMTYyNC0wLjA1NzIgMC4yNTg4IDAgMC4wOTYzIDAuMDIyNCAwLjE4NDggMC4wNjcyIDAuMjY1NSAwLjA0NDggMC4wNzg0IDAuMTA5OCAwLjE0IDAuMTk0OSAwLjE4NDggMC4wODc0IDAuMDQ0OCAwLjE5MjcgMC4wNjcyIDAuMzE1OSAwLjA2NzIgMC4xNjU4IDAgMC4zMTAzLTAuMDMzNiAwLjQzMzUtMC4xMDA4IDAuMTI1NS0wLjA2OTUgMC4yMjQxLTAuMTUzNSAwLjI5NTgtMC4yNTIxIDAuMDcxNy0wLjEwMDggMC4xMDk3LTAuMTk2IDAuMTE0Mi0wLjI4NTZsMC4yNjIxIDAuMzU5NmMtMC4wMjY4IDAuMDkxOC0wLjA3MjggMC4xOTA0LTAuMTM3NyAwLjI5NTctMC4wNjUgMC4xMDUzLTAuMTUwMSAwLjIwNjEtMC4yNTU0IDAuMzAyNC0wLjEwMzEgMC4wOTQxLTAuMjI3NCAwLjE3MTQtMC4zNzMxIDAuMjMxOS0wLjE0MzMgMC4wNjA1LTAuMzA5MSAwLjA5MDgtMC40OTczIDAuMDkwOC0wLjIzNzUgMC0wLjQ0OTItMC4wNDcxLTAuNjM1MS0wLjE0MTItMC4xODYtMC4wOTYzLTAuMzMxNi0wLjIyNTEtMC40MzY5LTAuMzg2NC0wLjEwNTMtMC4xNjM2LTAuMTU4LTAuMzQ4NC0wLjE1OC0wLjU1NDUgMC0wLjE5MjcgMC4wMzU5LTAuMzYzIDAuMTA3Ni0wLjUxMDggMC4wNzM5LTAuMTUwMSAwLjE4MTQtMC4yNzU2IDAuMzIyNi0wLjM3NjQgMC4xNDM0LTAuMTAwOCAwLjMxODEtMC4xNzcgMC41MjQyLTAuMjI4NSAwLjIwNjEtMC4wNTM4IDAuNDQxNC0wLjA4MDcgMC43MDU3LTAuMDgwN2gwLjYzNTJ6bTMuMzUyNy0xLjQyNDh2MC41OTE0aC0yLjA1di0wLjU5MTRoMi4wNXptLTEuNDU4NS0wLjg5MDZoMC44MDk5djMuNTIxOWMwIDAuMTEyIDAuMDE1NyAwLjE5ODIgMC4wNDcgMC4yNTg3IDAuMDMzNiAwLjA1ODMgMC4wNzk2IDAuMDk3NSAwLjEzNzggMC4xMTc2IDAuMDU4MyAwLjAyMDIgMC4xMjY2IDAuMDMwMyAwLjIwNSAwLjAzMDMgMC4wNTYgMCAwLjEwOTgtMC4wMDM0IDAuMTYxMy0wLjAxMDFzMC4wOTMtMC4wMTM0IDAuMTI0My0wLjAyMDJsMC4wMDM0IDAuNjE4NGMtMC4wNjcyIDAuMDIwMS0wLjE0NTYgMC4wMzgxLTAuMjM1MiAwLjA1MzctMC4wODc0IDAuMDE1Ny0wLjE4ODIgMC4wMjM2LTAuMzAyNSAwLjAyMzYtMC4xODU5IDAtMC4zNTA2LTAuMDMyNS0wLjQ5NC0wLjA5NzUtMC4xNDM0LTAuMDY3Mi0wLjI1NTQtMC4xNzU5LTAuMzM2LTAuMzI2LTAuMDgwNy0wLjE1MDEtMC4xMjEtMC4zNDk1LTAuMTIxLTAuNTk4MXYtMy41NzIzem0zLjgyOTkgNC41OTM5Yy0wLjI2ODkgMC0wLjUxMi0wLjA0MzctMC43MjkzLTAuMTMxMS0wLjIxNS0wLjA4OTYtMC4zOTg3LTAuMjE0LTAuNTUxMS0wLjM3My0wLjE1MDEtMC4xNTkxLTAuMjY1NS0wLjM0NjItMC4zNDYxLTAuNTYxMi0wLjA4MDctMC4yMTUxLTAuMTIxLTAuNDQ3LTAuMTIxLTAuNjk1N3YtMC4xMzQ0YzAtMC4yODQ1IDAuMDQxNC0wLjU0MjEgMC4xMjQzLTAuNzcyOXMwLjE5ODMtMC40Mjc5IDAuMzQ2Mi0wLjU5MTRjMC4xNDc4LTAuMTY1OCAwLjMyMjYtMC4yOTI0IDAuNTI0Mi0wLjM3OThzMC40MjAxLTAuMTMxIDAuNjU1My0wLjEzMWMwLjI1OTkgMCAwLjQ4NzMgMC4wNDM2IDAuNjgyMiAwLjEzMXMwLjM1NjIgMC4yMTA2IDAuNDgzOSAwLjM2OTdjMC4xMyAwLjE1NjggMC4yMjYzIDAuMzQzOSAwLjI4OSAwLjU2MTIgMC4wNjUgMC4yMTczIDAuMDk3NSAwLjQ1NyAwLjA5NzUgMC43MTkxdjAuMzQ2MmgtMi44MDk0di0wLjU4MTRoMi4wMDk2di0wLjA2MzljLTAuMDA0NS0wLjE0NTYtMC4wMzM2LTAuMjgyMi0wLjA4NzQtMC40MDk5LTAuMDUxNS0wLjEyNzctMC4xMzExLTAuMjMwOC0wLjIzODYtMC4zMDkycy0wLjI1MDktMC4xMTc2LTAuNDMwMS0wLjExNzZjLTAuMTM0NSAwLTAuMjU0MyAwLjAyOTEtMC4zNTk2IDAuMDg3My0wLjEwMzEgMC4wNTYxLTAuMTg5MyAwLjEzNzgtMC4yNTg4IDAuMjQ1NC0wLjA2OTQgMC4xMDc1LTAuMTIzMiAwLjIzNzQtMC4xNjEzIDAuMzg5OC0wLjAzNTggMC4xNTAxLTAuMDUzOCAwLjMxOTItMC4wNTM4IDAuNTA3NHYwLjEzNDRjMCAwLjE1OTEgMC4wMjEzIDAuMzA3IDAuMDYzOSAwLjQ0MzYgMC4wNDQ4IDAuMTM0NSAwLjEwOTggMC4yNTIxIDAuMTk0OSAwLjM1MjlzMC4xODgyIDAuMTgwMyAwLjMwOTIgMC4yMzg2YzAuMTIwOSAwLjA1NiAwLjI1ODcgMC4wODQgMC40MTMzIDAuMDg0IDAuMTk0OSAwIDAuMzY4Ni0wLjAzOTIgMC41MjA5LTAuMTE3NnMwLjI4NDUtMC4xODkzIDAuMzk2NS0wLjMzMjdsMC40MjY4IDAuNDEzM2MtMC4wNzg0IDAuMTE0My0wLjE4MDMgMC4yMjQxLTAuMzA1OCAwLjMyOTQtMC4xMjU0IDAuMTAzLTAuMjc4OSAwLjE4Ny0wLjQ2MDQgMC4yNTItMC4xNzkyIDAuMDY1LTAuMzg3NiAwLjA5NzUtMC42MjUgMC4wOTc1em02LjI1MTctNC45Nzd2NC45MDk3aC0wLjgwOTl2LTMuOTQ4NmwtMS4xOTk3IDAuNDA2N3YtMC42Njg4bDEuOTEyMS0wLjY5OWgwLjA5NzV6bTQuMTA4OCA0LjE1N3YtNC40MDloMC44MTMydjUuMTYxN2gtMC43MzU5bC0wLjA3NzMtMC43NTI3em0tMi4zNjU4LTEuMDI1di0wLjA3MDVjMC0wLjI3NTYgMC4wMzI0LTAuNTI2NSAwLjA5NzQtMC43NTI4IDAuMDY1LTAuMjI4NSAwLjE1OTEtMC40MjQ1IDAuMjgyMy0wLjU4ODEgMC4xMjMyLTAuMTY1OCAwLjI3MzMtMC4yOTI0IDAuNDUwMy0wLjM3OTcgMC4xNzctMC4wODk2IDAuMzc2NC0wLjEzNDQgMC41OTgyLTAuMTM0NCAwLjIxOTUgMCAwLjQxMjIgMC4wNDI1IDAuNTc4IDAuMTI3NyAwLjE2NTggMC4wODUxIDAuMzA2OSAwLjIwNzIgMC40MjM0IDAuMzY2MiAwLjExNjUgMC4xNTY5IDAuMjA5NSAwLjM0NTEgMC4yNzg5IDAuNTY0NiAwLjA2OTUgMC4yMTczIDAuMTE4OCAwLjQ1OTMgMC4xNDc5IDAuNzI1OXYwLjIyNTFjLTAuMDI5MSAwLjI1OTktMC4wNzg0IDAuNDk3NC0wLjE0NzkgMC43MTI1LTAuMDY5NCAwLjIxNS0wLjE2MjQgMC40MDEtMC4yNzg5IDAuNTU3OHMtMC4yNTg3IDAuMjc3OC0wLjQyNjggMC4zNjNjLTAuMTY1OCAwLjA4NTEtMC4zNTk1IDAuMTI3Ny0wLjU4MTMgMC4xMjc3LTAuMjE5NiAwLTAuNDE3OS0wLjA0Ni0wLjU5NDktMC4xMzc4LTAuMTc0Ny0wLjA5MTktMC4zMjM3LTAuMjIwNy0wLjQ0NjktMC4zODY1cy0wLjIxNzMtMC4zNjA3LTAuMjgyMy0wLjU4NDdjLTAuMDY1LTAuMjI2My0wLjA5NzQtMC40NzE2LTAuMDk3NC0wLjczNnptMC44MDk4LTAuMDcwNXYwLjA3MDVjMCAwLjE2NTggMC4wMTQ2IDAuMzIwNCAwLjA0MzcgMC40NjM4IDAuMDMxNCAwLjE0MzQgMC4wNzk2IDAuMjY5OSAwLjE0NDUgMC4zNzk3IDAuMDY1IDAuMTA3NiAwLjE0OSAwLjE5MjcgMC4yNTIxIDAuMjU1NCAwLjEwNTMgMC4wNjA1IDAuMjMwNyAwLjA5MDggMC4zNzYzIDAuMDkwOCAwLjE4MzggMCAwLjMzNS0wLjA0MDQgMC40NTM3LTAuMTIxIDAuMTE4OC0wLjA4MDcgMC4yMTE3LTAuMTg5MyAwLjI3ODktMC4zMjYgMC4wNjk1LTAuMTM4OSAwLjExNjUtMC4yOTM1IDAuMTQxMi0wLjQ2Mzd2LTAuNjA4M2MtMC4wMTM1LTAuMTMyMi0wLjA0MTUtMC4yNTU0LTAuMDg0LTAuMzY5Ny0wLjA0MDQtMC4xMTQyLTAuMDk1Mi0wLjIxMzktMC4xNjQ3LTAuMjk5LTAuMDY5NC0wLjA4NzQtMC4xNTU3LTAuMTU0Ni0wLjI1ODgtMC4yMDE3LTAuMTAwOC0wLjA0OTMtMC4yMjA2LTAuMDczOS0wLjM1OTUtMC4wNzM5LTAuMTQ3OSAwLTAuMjczNCAwLjAzMTQtMC4zNzY0IDAuMDk0MS0wLjEwMzEgMC4wNjI3LTAuMTg4MiAwLjE0OS0wLjI1NTQgMC4yNTg3LTAuMDY1IDAuMTA5OC0wLjExMzEgMC4yMzc1LTAuMTQ0NSAwLjM4MzEtMC4wMzE0IDAuMTQ1Ny0wLjA0NzEgMC4zMDE0LTAuMDQ3MSAwLjQ2NzJ6bTcuMjY2NiAxLjExOXYtMS43MzRjMC0wLjEzLTAuMDIzNS0wLjI0Mi0wLjA3MDYtMC4zMzYxLTAuMDQ3LTAuMDk0MS0wLjExODctMC4xNjY5LTAuMjE1LTAuMjE4NC0wLjA5NDEtMC4wNTE1LTAuMjEyOS0wLjA3NzMtMC4zNTYyLTAuMDc3My0wLjEzMjIgMC0wLjI0NjUgMC4wMjI0LTAuMzQyOCAwLjA2NzItMC4wOTY0IDAuMDQ0OC0wLjE3MTQgMC4xMDUzLTAuMjI1MiAwLjE4MTUtMC4wNTM3IDAuMDc2Mi0wLjA4MDYgMC4xNjI0LTAuMDgwNiAwLjI1ODdoLTAuODA2NmMwLTAuMTQzMyAwLjAzNDgtMC4yODIyIDAuMTA0Mi0wLjQxNjcgMC4wNjk1LTAuMTM0NCAwLjE3MDMtMC4yNTQyIDAuMzAyNS0wLjM1OTUgMC4xMzIxLTAuMTA1MyAwLjI5MDEtMC4xODgyIDAuNDczOC0wLjI0ODdzMC4zODk4LTAuMDkwNyAwLjYxODMtMC4wOTA3YzAuMjczNCAwIDAuNTE1MyAwLjA0NTkgMC43MjU5IDAuMTM3NyAwLjIxMjggMC4wOTE5IDAuMzc5OCAwLjIzMDggMC41MDA3IDAuNDE2NyAwLjEyMzIgMC4xODM3IDAuMTg0OSAwLjQxNDUgMC4xODQ5IDAuNjkyM3YxLjYxNjRjMCAwLjE2NTggMC4wMTEyIDAuMzE0OCAwLjAzMzYgMC40NDcgMC4wMjQ2IDAuMTI5OSAwLjA1OTMgMC4yNDMgMC4xMDQxIDAuMzM5NHYwLjA1MzdoLTAuODNjLTAuMDM4MS0wLjA4NzMtMC4wNjgzLTAuMTk4Mi0wLjA5MDctMC4zMzI2LTAuMDIwMi0wLjEzNjctMC4wMzAzLTAuMjY4OS0wLjAzMDMtMC4zOTY2em0wLjExNzYtMS40ODIgMC4wMDY4IDAuNTAwN2gtMC41ODE0Yy0wLjE1MDEgMC0wLjI4MjMgMC4wMTQ2LTAuMzk2NiAwLjA0MzctMC4xMTQyIDAuMDI2OS0wLjIwOTQgMC4wNjcyLTAuMjg1NiAwLjEyMXMtMC4xMzMzIDAuMTE4Ny0wLjE3MTQgMC4xOTQ5LTAuMDU3MSAwLjE2MjQtMC4wNTcxIDAuMjU4OGMwIDAuMDk2MyAwLjAyMjQgMC4xODQ4IDAuMDY3MiAwLjI2NTUgMC4wNDQ4IDAuMDc4NCAwLjEwOTggMC4xNCAwLjE5NDkgMC4xODQ4IDAuMDg3NCAwLjA0NDggMC4xOTI3IDAuMDY3MiAwLjMxNTkgMC4wNjcyIDAuMTY1OCAwIDAuMzEwMy0wLjAzMzYgMC40MzM1LTAuMTAwOCAwLjEyNTUtMC4wNjk1IDAuMjI0LTAuMTUzNSAwLjI5NTctMC4yNTIxIDAuMDcxNy0wLjEwMDggMC4xMDk4LTAuMTk2IDAuMTE0My0wLjI4NTZsMC4yNjIxIDAuMzU5NmMtMC4wMjY5IDAuMDkxOC0wLjA3MjggMC4xOTA0LTAuMTM3OCAwLjI5NTdzLTAuMTUwMSAwLjIwNjEtMC4yNTU0IDAuMzAyNGMtMC4xMDMgMC4wOTQxLTAuMjI3NCAwLjE3MTQtMC4zNzMgMC4yMzE5LTAuMTQzNCAwLjA2MDUtMC4zMDkyIDAuMDkwOC0wLjQ5NzQgMC4wOTA4LTAuMjM3NCAwLTAuNDQ5MS0wLjA0NzEtMC42MzUxLTAuMTQxMi0wLjE4NTktMC4wOTYzLTAuMzMxNi0wLjIyNTEtMC40MzY5LTAuMzg2NC0wLjEwNTMtMC4xNjM2LTAuMTU3OS0wLjM0ODQtMC4xNTc5LTAuNTU0NSAwLTAuMTkyNyAwLjAzNTgtMC4zNjMgMC4xMDc1LTAuNTEwOCAwLjA3NC0wLjE1MDEgMC4xODE1LTAuMjc1NiAwLjMyMjYtMC4zNzY0IDAuMTQzNC0wLjEwMDggMC4zMTgyLTAuMTc3IDAuNTI0My0wLjIyODUgMC4yMDYxLTAuMDUzOCAwLjQ0MTMtMC4wODA3IDAuNzA1Ny0wLjA4MDdoMC42MzUxem00LjAxNDktMS40MjQ4aDAuNzM2djMuNTM1MmMwIDAuMzI3MS0wLjA3IDAuNjA0OS0wLjIwOSAwLjgzMzQtMC4xMzggMC4yMjg2LTAuMzMyIDAuNDAyMi0wLjU4MSAwLjUyMDktMC4yNDkgMC4xMjEtMC41MzYgMC4xODE1LTAuODY0IDAuMTgxNS0wLjEzOCAwLTAuMjkzLTAuMDIwMi0wLjQ2My0wLjA2MDUtMC4xNjgtMC4wNDAzLTAuMzMyLTAuMTA1My0wLjQ5MS0wLjE5NDktMC4xNTctMC4wODc0LTAuMjg4LTAuMjAyOC0wLjM5My0wLjM0NjFsMC4zOC0wLjQ3NzJjMC4xMyAwLjE1NDUgMC4yNzMgMC4yNjc3IDAuNDMgMC4zMzk0czAuMzIxIDAuMTA3NSAwLjQ5NCAwLjEwNzVjMC4xODYgMCAwLjM0NC0wLjAzNDcgMC40NzQtMC4xMDQyIDAuMTMyLTAuMDY3MiAwLjIzNC0wLjE2NjkgMC4zMDUtMC4yOTkgMC4wNzItMC4xMzIyIDAuMTA4LTAuMjkzNSAwLjEwOC0wLjQ4NHYtMi43Mjg3bDAuMDc0LTAuODIzM3ptLTIuNDcgMS44NTgzdi0wLjA3MDVjMC0wLjI3NTYgMC4wMzMtMC41MjY1IDAuMTAxLTAuNzUyOCAwLjA2Ny0wLjIyODUgMC4xNjMtMC40MjQ1IDAuMjg5LTAuNTg4MSAwLjEyNS0wLjE2NTggMC4yNzctMC4yOTI0IDAuNDU3LTAuMzc5NyAwLjE3OS0wLjA4OTYgMC4zODItMC4xMzQ0IDAuNjA4LTAuMTM0NCAwLjIzNSAwIDAuNDM2IDAuMDQyNSAwLjYwMSAwLjEyNzcgMC4xNjkgMC4wODUxIDAuMzA5IDAuMjA3MiAwLjQyMSAwLjM2NjIgMC4xMTIgMC4xNTY5IDAuMTk5IDAuMzQ1MSAwLjI2MiAwLjU2NDYgMC4wNjUgMC4yMTczIDAuMTEzIDAuNDU5MyAwLjE0NCAwLjcyNTl2MC4yMjUxYy0wLjAyOSAwLjI1OTktMC4wNzggMC40OTc0LTAuMTQ4IDAuNzEyNS0wLjA2OSAwLjIxNS0wLjE2MSAwLjQwMS0wLjI3NSAwLjU1NzgtMC4xMTUgMC4xNTY4LTAuMjU2IDAuMjc3OC0wLjQyNCAwLjM2My0wLjE2NSAwLjA4NTEtMC4zNjEgMC4xMjc3LTAuNTg4IDAuMTI3Ny0wLjIyMiAwLTAuNDIyLTAuMDQ2LTAuNjAxLTAuMTM3OC0wLjE3Ny0wLjA5MTktMC4zMy0wLjIyMDctMC40NTctMC4zODY1LTAuMTI2LTAuMTY1OC0wLjIyMi0wLjM2MDctMC4yODktMC41ODQ3LTAuMDY4LTAuMjI2My0wLjEwMS0wLjQ3MTYtMC4xMDEtMC43MzZ6bTAuODEtMC4wNzA1djAuMDcwNWMwIDAuMTY1OCAwLjAxNSAwLjMyMDQgMC4wNDcgMC40NjM4IDAuMDMzIDAuMTQzNCAwLjA4NCAwLjI2OTkgMC4xNTEgMC4zNzk3IDAuMDY5IDAuMTA3NiAwLjE1NyAwLjE5MjcgMC4yNjIgMC4yNTU0IDAuMTA4IDAuMDYwNSAwLjIzNCAwLjA5MDggMC4zOCAwLjA5MDggMC4xOSAwIDAuMzQ2LTAuMDQwNCAwLjQ2Ny0wLjEyMSAwLjEyMy0wLjA4MDcgMC4yMTctMC4xODkzIDAuMjgyLTAuMzI2IDAuMDY3LTAuMTM4OSAwLjExNS0wLjI5MzUgMC4xNDEtMC40NjM3di0wLjYwODNjLTAuMDEzLTAuMTMyMi0wLjA0MS0wLjI1NTQtMC4wODQtMC4zNjk3LTAuMDQtMC4xMTQyLTAuMDk1LTAuMjEzOS0wLjE2NC0wLjI5OS0wLjA3LTAuMDg3NC0wLjE1Ny0wLjE1NDYtMC4yNjItMC4yMDE3LTAuMTA2LTAuMDQ5My0wLjIzLTAuMDczOS0wLjM3My0wLjA3MzktMC4xNDYgMC0wLjI3MyAwLjAzMTQtMC4zOCAwLjA5NDEtMC4xMDggMC4wNjI3LTAuMTk2IDAuMTQ5LTAuMjY2IDAuMjU4Ny0wLjA2NyAwLjEwOTgtMC4xMTcgMC4yMzc1LTAuMTUxIDAuMzgzMS0wLjAzMyAwLjE0NTctMC4wNSAwLjMwMTQtMC4wNSAwLjQ2NzJ6bTMuMjI1IDAuMDcwNXYtMC4wNzczYzAtMC4yNjIxIDAuMDM4LTAuNTA1MiAwLjExNC0wLjcyOTIgMC4wNzYtMC4yMjYzIDAuMTg2LTAuNDIyMyAwLjMyOS0wLjU4ODEgMC4xNDYtMC4xNjggMC4zMjMtMC4yOTggMC41MzEtMC4zODk4IDAuMjExLTAuMDk0MSAwLjQ0OC0wLjE0MTEgMC43MTMtMC4xNDExIDAuMjY2IDAgMC41MDQgMC4wNDcgMC43MTIgMC4xNDExIDAuMjExIDAuMDkxOCAwLjM4OSAwLjIyMTggMC41MzQgMC4zODk4IDAuMTQ2IDAuMTY1OCAwLjI1NyAwLjM2MTggMC4zMzMgMC41ODgxIDAuMDc2IDAuMjI0IDAuMTE0IDAuNDY3MSAwLjExNCAwLjcyOTJ2MC4wNzczYzAgMC4yNjIyLTAuMDM4IDAuNTA1Mi0wLjExNCAwLjcyOTMtMC4wNzYgMC4yMjQtMC4xODcgMC40Mi0wLjMzMyAwLjU4ODEtMC4xNDUgMC4xNjU3LTAuMzIyIDAuMjk1Ny0wLjUzMSAwLjM4OTgtMC4yMDggMC4wOTE4LTAuNDQ0IDAuMTM3OC0wLjcwOSAwLjEzNzgtMC4yNjYgMC0wLjUwNS0wLjA0Ni0wLjcxNS0wLjEzNzgtMC4yMDktMC4wOTQxLTAuMzg2LTAuMjI0MS0wLjUzMS0wLjM4OTgtMC4xNDYtMC4xNjgxLTAuMjU3LTAuMzY0MS0wLjMzMy0wLjU4ODEtMC4wNzYtMC4yMjQxLTAuMTE0LTAuNDY3MS0wLjExNC0wLjcyOTN6bTAuODEtMC4wNzczdjAuMDc3M2MwIDAuMTYzNiAwLjAxNiAwLjMxODIgMC4wNSAwLjQ2MzhzMC4wODYgMC4yNzMzIDAuMTU4IDAuMzgzMSAwLjE2NCAwLjE5NiAwLjI3NiAwLjI1ODdjMC4xMTIgMC4wNjI4IDAuMjQ1IDAuMDk0MSAwLjM5OSAwLjA5NDEgMC4xNTEgMCAwLjI4LTAuMDMxMyAwLjM5LTAuMDk0MSAwLjExMi0wLjA2MjcgMC4yMDQtMC4xNDg5IDAuMjc2LTAuMjU4N3MwLjEyNC0wLjIzNzUgMC4xNTgtMC4zODMxYzAuMDM2LTAuMTQ1NiAwLjA1NC0wLjMwMDIgMC4wNTQtMC40NjM4di0wLjA3NzNjMC0wLjE2MTMtMC4wMTgtMC4zMTM2LTAuMDU0LTAuNDU3LTAuMDM0LTAuMTQ1Ni0wLjA4OC0wLjI3NDQtMC4xNjItMC4zODY1LTAuMDcxLTAuMTEyLTAuMTYzLTAuMTk5My0wLjI3NS0wLjI2MjEtMC4xMS0wLjA2NDktMC4yNDEtMC4wOTc0LTAuMzkzLTAuMDk3NC0wLjE1MyAwLTAuMjg1IDAuMDMyNS0wLjM5NyAwLjA5NzQtMC4xMSAwLjA2MjgtMC4yIDAuMTUwMS0wLjI3MiAwLjI2MjEtMC4wNzIgMC4xMTIxLTAuMTI0IDAuMjQwOS0wLjE1OCAwLjM4NjUtMC4wMzQgMC4xNDM0LTAuMDUgMC4yOTU3LTAuMDUgMC40NTd6IiBmaWxsLW9wYWNpdHk9Ii4zOCIvPgogICA8cGF0aCBkPSJtNDguMTk2IDgwLjQ2OXYyLjc5NTloLTE0LjIxM3YtMi40MDI3bDYuOTAyNS03LjUyODdjMC43NTcyLTAuODU0MyAxLjM1NDMtMS41OTIyIDEuNzkxMS0yLjIxMzUgMC40MzY5LTAuNjIxMyAwLjc0MjctMS4xNzk1IDAuOTE3NS0xLjY3NDYgMC4xODQ0LTAuNTA0OSAwLjI3NjYtMC45OTUxIDAuMjc2Ni0xLjQ3MDggMC0wLjY2OTktMC4xMjYyLTEuMjU3Mi0wLjM3ODYtMS43NjIxLTAuMjQyNy0wLjUxNDUtMC42MDE5LTAuOTE3NC0xLjA3NzYtMS4yMDg2LTAuNDc1Ny0wLjMwMS0xLjA1MzMtMC40NTE1LTEuNzMyOS0wLjQ1MTUtMC43ODY0IDAtMS40NDY1IDAuMTY5OS0xLjk4MDUgMC41MDk3LTAuNTMzOSAwLjMzOTgtMC45MzY4IDAuODEwNi0xLjIwODYgMS40MTI2LTAuMjcxOSAwLjU5MjEtMC40MDc4IDEuMjcxNy0wLjQwNzggMi4wMzg3aC0zLjUwOTVjMC0xLjIzMyAwLjI4MTYtMi4zNTkxIDAuODQ0Ni0zLjM3ODUgMC41NjMxLTEuMDI5IDEuMzc4Ni0xLjg0NDUgMi40NDY1LTIuNDQ2NCAxLjA2NzktMC42MTE3IDIuMzU0Mi0wLjkxNzUgMy44NTktMC45MTc1IDEuNDE3NCAwIDIuNjIxMiAwLjIzNzkgMy42MTE0IDAuNzEzNiAwLjk5MDMgMC40NzU3IDEuNzQyNyAxLjE1MDQgMi4yNTcyIDIuMDI0MSAwLjUyNDIgMC44NzM4IDAuNzg2NCAxLjkwNzcgMC43ODY0IDMuMTAxOCAwIDAuNjYwMi0wLjEwNjggMS4zMTU1LTAuMzIwNCAxLjk2NTktMC4yMTM2IDAuNjUwNS0wLjUxOTQgMS4zMDA5LTAuOTE3NCAxLjk1MTQtMC4zODg0IDAuNjQwNy0wLjg0OTUgMS4yODYzLTEuMzgzNSAxLjkzNjctMC41MzM5IDAuNjQwOC0xLjEyMTIgMS4yOTEyLTEuNzYyIDEuOTUxNGwtNC41ODcxIDUuMDUzMWg5Ljc4NTh6bTE2LjQyOSAwdjIuNzk1OWgtMTQuMjEzdi0yLjQwMjdsNi45MDI2LTcuNTI4N2MwLjc1NzItMC44NTQzIDEuMzU0Mi0xLjU5MjIgMS43OTExLTIuMjEzNXMwLjc0MjctMS4xNzk1IDAuOTE3NC0xLjY3NDZjMC4xODQ1LTAuNTA0OSAwLjI3NjctMC45OTUxIDAuMjc2Ny0xLjQ3MDggMC0wLjY2OTktMC4xMjYyLTEuMjU3Mi0wLjM3ODYtMS43NjIxLTAuMjQyNy0wLjUxNDUtMC42MDE5LTAuOTE3NC0xLjA3NzYtMS4yMDg2LTAuNDc1Ny0wLjMwMS0xLjA1MzMtMC40NTE1LTEuNzMyOS0wLjQ1MTUtMC43ODY0IDAtMS40NDY1IDAuMTY5OS0xLjk4MDUgMC41MDk3LTAuNTMzOSAwLjMzOTgtMC45MzY4IDAuODEwNi0xLjIwODcgMS40MTI2LTAuMjcxOCAwLjU5MjEtMC40MDc3IDEuMjcxNy0wLjQwNzcgMi4wMzg3aC0zLjUwOTVjMC0xLjIzMyAwLjI4MTUtMi4zNTkxIDAuODQ0Ni0zLjM3ODUgMC41NjMxLTEuMDI5IDEuMzc4Ni0xLjg0NDUgMi40NDY1LTIuNDQ2NCAxLjA2NzktMC42MTE3IDIuMzU0Mi0wLjkxNzUgMy44NTktMC45MTc1IDEuNDE3NCAwIDIuNjIxMiAwLjIzNzkgMy42MTE0IDAuNzEzNnMxLjc0MjYgMS4xNTA0IDIuMjU3MiAyLjAyNDFjMC41MjQyIDAuODczOCAwLjc4NjMgMS45MDc3IDAuNzg2MyAzLjEwMTggMCAwLjY2MDItMC4xMDY4IDEuMzE1NS0wLjMyMDMgMS45NjU5LTAuMjEzNiAwLjY1MDUtMC41MTk0IDEuMzAwOS0wLjkxNzUgMS45NTE0LTAuMzg4MyAwLjY0MDctMC44NDk0IDEuMjg2My0xLjM4MzQgMS45MzY3LTAuNTMzOSAwLjY0MDgtMS4xMjEzIDEuMjkxMi0xLjc2MiAxLjk1MTRsLTQuNTg3MSA1LjA1MzFoOS43ODU4em0yLjQ5MjUtMTQuODFjMC0wLjcwODcgMC4xNzQ3LTEuMzU5MiAwLjUyNDItMS45NTE0czAuODE1NS0xLjA2MyAxLjM5OC0xLjQxMjVjMC41OTIyLTAuMzU5MiAxLjIzMjktMC41Mzg4IDEuOTIyMi0wLjUzODggMC42OTkgMCAxLjMzNDkgMC4xNzk2IDEuOTA3NyAwLjUzODggMC41NzI4IDAuMzQ5NSAxLjAyOTEgMC44MjAzIDEuMzY4OCAxLjQxMjUgMC4zNDk1IDAuNTkyMiAwLjUyNDMgMS4yNDI3IDAuNTI0MyAxLjk1MTRzLTAuMTc0OCAxLjM1OTEtMC41MjQzIDEuOTUxM2MtMC4zMzk3IDAuNTgyNS0wLjc5NiAxLjA0MzYtMS4zNjg4IDEuMzgzNHMtMS4yMDg3IDAuNTA5Ny0xLjkwNzcgMC41MDk3Yy0wLjY4OTMgMC0xLjMzLTAuMTY5OS0xLjkyMjItMC41MDk3LTAuNTgyNS0wLjMzOTgtMS4wNDg1LTAuODAwOS0xLjM5OC0xLjM4MzQtMC4zNDk1LTAuNTkyMi0wLjUyNDItMS4yNDI2LTAuNTI0Mi0xLjk1MTN6bTEuOTY1OSAwYzAgMC41MjQyIDAuMTg0NSAwLjk2NTkgMC41NTM0IDEuMzI1MSAwLjM2ODkgMC4zNDk1IDAuODEwNiAwLjUyNDMgMS4zMjUxIDAuNTI0MyAwLjUxNDYgMCAwLjk0NjYtMC4xNzQ4IDEuMjk2MS0wLjUyNDNzMC41MjQyLTAuNzkxMiAwLjUyNDItMS4zMjUxYzAtMC41NDM3LTAuMTc0Ny0wLjk5NTEtMC41MjQyLTEuMzU0M3MtMC43ODE1LTAuNTM4OC0xLjI5NjEtMC41Mzg4Yy0wLjUxNDUgMC0wLjk1NjIgMC4xNzk2LTEuMzI1MSAwLjUzODhzLTAuNTUzNCAwLjgxMDYtMC41NTM0IDEuMzU0M3ptMjEuNzI5IDEwLjcwM2gzLjY0MDZjLTAuMTE2NSAxLjM4ODMtMC41MDQ4IDIuNjI2MS0xLjE2NSAzLjcxMzQtMC42NjAxIDEuMDc3Ni0xLjU4NzMgMS45MjcxLTIuNzgxNCAyLjU0ODRzLTIuNjQ1NCAwLjkzMi00LjM1NDEgMC45MzJjLTEuMzEwNiAwLTIuNDkwMS0wLjIzMy0zLjUzODYtMC42OTktMS4wNDg1LTAuNDc1Ny0xLjk0NjUtMS4xNDU2LTIuNjk0LTIuMDA5Ni0wLjc0NzYtMC44NzM3LTEuMzIwNC0xLjkyNzEtMS43MTg0LTMuMTYtMC4zODgzLTEuMjMyOS0wLjU4MjUtMi42MTE1LTAuNTgyNS00LjEzNTd2LTEuNzYyYzAtMS41MjQyIDAuMTk5LTIuOTAyOCAwLjU5NzEtNC4xMzU3IDAuNDA3Ny0xLjIzMjkgMC45OTAyLTIuMjg2MyAxLjc0NzQtMy4xNiAwLjc1NzMtMC44ODM1IDEuNjY1LTEuNTU4MiAyLjcyMzItMi4wMjQyIDEuMDY3OS0wLjQ2NiAyLjI2NjktMC42OTkgMy41OTY5LTAuNjk5IDEuNjg5MiAwIDMuMTE2MyAwLjMxMDcgNC4yODEzIDAuOTMyczIuMDY3OCAxLjQ4MDUgMi43MDg2IDIuNTc3NWMwLjY1MDQgMS4wOTcxIDEuMDQ4NCAyLjM1NDMgMS4xOTQxIDMuNzcxN2gtMy42NDA2Yy0wLjA5NzEtMC45MTI2LTAuMzEwNy0xLjY5NDEtMC42NDA3LTIuMzQ0Ni0wLjMyMDQtMC42NTA0LTAuNzk2MS0xLjE0NTUtMS40MjcxLTEuNDg1My0wLjYzMTEtMC4zNDk1LTEuNDU2My0wLjUyNDItMi40NzU2LTAuNTI0Mi0wLjgzNDkgMC0xLjU2MyAwLjE1NTMtMi4xODQ0IDAuNDY1OS0wLjYyMTMgMC4zMTA3LTEuMTQwNyAwLjc2Ny0xLjU1ODEgMS4zNjg5LTAuNDE3NSAwLjYwMTktMC43MzMgMS4zNDQ2LTAuOTQ2NiAyLjIyOC0wLjIwMzkgMC44NzM4LTAuMzA1OCAxLjg3MzctMC4zMDU4IDIuOTk5OXYxLjc5MTFjMCAxLjA2NzkgMC4wOTIyIDIuMDM4NyAwLjI3NjcgMi45MTI1IDAuMTk0MiAwLjg2NCAwLjQ4NTQgMS42MDY3IDAuODczNyAyLjIyOCAwLjM5ODEgMC42MjEzIDAuOTAyOSAxLjEwMTkgMS41MTQ1IDEuNDQxNyAwLjYxMTYgMC4zMzk3IDEuMzQ0NiAwLjUwOTYgMi4xOTg5IDAuNTA5NiAxLjAzODggMCAxLjg3ODUtMC4xNjUgMi41MTkzLTAuNDk1MSAwLjY1MDQtMC4zMzAxIDEuMTQwNy0wLjgxMDYgMS40NzA4LTEuNDQxNiAwLjMzOTgtMC42NDA4IDAuNTYzLTEuNDIyMyAwLjY2OTgtMi4zNDQ2eiIgZmlsbC1vcGFjaXR5PSIuODciLz4KICA8L2c+CiA8L2c+CiA8ZGVmcz4KICA8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfMTE0Ml8yMDM5NTQiIHg9Ii45MTE3NiIgeT0iLjIwNTg4IiB3aWR0aD0iMTI2LjE4IiBoZWlnaHQ9IjEyNi4xOCIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICA8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgogICA8ZmVDb2xvck1hdHJpeCBpbj0iU291cmNlQWxwaGEiIHJlc3VsdD0iaGFyZEFscGhhIiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIi8+CiAgIDxmZU9mZnNldCBkeT0iMi4yOTQxMiIvPgogICA8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIyLjI5NDEyIi8+CiAgIDxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgogICA8ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDQgMCIvPgogICA8ZmVCbGVuZCBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfMTE0Ml8yMDM5NTQiLz4KICAgPGZlQmxlbmQgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9kcm9wU2hhZG93XzExNDJfMjAzOTU0IiByZXN1bHQ9InNoYXBlIi8+CiAgPC9maWx0ZXI+CiA8L2RlZnM+Cjwvc3ZnPgo=", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAYAAABJ/yOpAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAJFzSURBVHgBrb0HoCRHdSh6qmdu3qjNq7CrsCuJoAiSAGFyEtgWyOSc/J4fBgw48s0zD9vYxvAMziaDyLYJxiQRJFBCoCyiskDSBkmbbr53puvXSVWnqnvuXfm5pdmZ211d4dTJdeqUg3Cddv5fv935zhudgzX4tw//Ofwv3KDLh7+riv724Tde+Ds+l7/B0csAFf7Df2gZW9b+nb4reof/9vKx7dTyvAKPTYXHvnJZGe9rqCrXaCP1r5J7pm3vU9/NmLk/PvubipV1hvYQXlWAj/f90ISDiuqBCMM6vq9t23Z86j+1p+9p+1QofuuF7bX/lnoLOJT9bpuXRjmtw/FcVBZuwP3FW3jfEyh9MR8e2q66rrN2tBx+6yv0u8ZPnzEh/I3v4XN81u/XNN++z/f7/T7UvfAJ373w3V/sweLiPMxNz8ADd98Bnan74C1veC6cc9ZDGuNXfI/9B7gz/P7Y8ccf+/buGee/5+117f/EuzCNnl90kCOzToz3JcLXDDYBRkXIGwbvEWAeymqk8VhfTjT8uw5IVjlFIgEcCML6Zp/kaQBSL0MUfuITogETIMPASxtekC8RM9VWRRIy/STKh8Z4PCMSThaWq7hKZQ+mr4kw0xAE0byP7eMceBlT1QH6W5lDeof/1Hki2Mexu4jQWn5Jpjbgm5HfZwzFaxnHcNTy2pr2IYKrlVA8dDo6F7WBC7elxEOE4Gim+H2DgwFfqY5QNECmZoZSh0+454RJ1uF31a8i4wWqjwm5iszICbPlMfE8Uhvbw99/cscdd0E3NPLypShe7+skKJFwWRfxNEkbbhSaZJaAXE6GaZY7z/V6qYPQ1qe+MB4pUKtscmy/BaIGN7XPiv5MEIzcFYAVXqbzCQGZw9m2fOyTz4iW55RkSwEJ1wLjKtIQw6Yj7boEqwK5GSQ+49Y0wQFhKldlMCgJw/a/IUkqrjybI+1YVt4yDikl804ErYiosIGSIeh8MiP0wggQ8RUfGIEF//r4rEMExLABqYsZWqcjYAk/qhrnqxa4VhnsXETAiqQ/T6iLMKXhK6EAvLEbvrbbDpWAs1KjJKBcAkQ4gWsySwMQnxcGiEg+8EW5vHClOvLmhJAJu5ttxvdt3S7VgOqjVM4wA9fyfmIGESaVEmmiKGfK0cQ0+ucacOP6pA/OIC+oFOaJ9DAA5gCZFGx7VhJJ/k7F0kL6DGU7rokbSUgkIrDqaM5UU0VKWJbBJmlUJQboKxkzayOqWlWi6jOhpL72+W1utxPmESVKxUSQGAyr5SIweAyqNQBLEG7TE451wK3plrpgG2HkAG0CT7+8qAKqxrSWz9qKeMn3uFOxDPfXZ43QRAjnZ8RULgoZ52mbbIO3OitSVhBTxHp5JU6ZI0mUpqD9MmoOJFXVi3o3iPgtEWr/EgJ5sa3aub5F/AyJG3VDlDLNMVnpV9SvHYrP9M8kZXLGl5hA7JuDXMdvwStlEqRiCRx9/M8R0tNvsktqqdtDA7VwKFimQmLqZzYpMUDh4KUmo+1V5hl+d22HLXcfxKnykeU8PKkxh/l+vGoSsWwX1FGxqqnDRYPSWq6iNY1DJ+pg9nbF9UaOJUiiYj4itkU4pU1DENQD/R25X3hPiC6poZDKVO3MoslsVN1oEqNKJGtzeFEhq4JILDlnCO9d1pfEEAzDyPqUE5QlLCsNlpRSURC69nmDJLU7pF46EZrMHFRzoL8D4iPXr9F+iQSSVG0qXKOa1ScYJbuI1VjvIFNby74Kbyd41qGubgQyLI3UpN/WBYJETg6kvDkScnV6XtSX7iUgAyQ9OwGae6nCIz6L5quMoqUd6xHJ1YIqNtkcZS4dsie1H4jcceykmnk29gRx+CGLpKRB5kiv44qTGOHS7JOd0Ka64xojiGUMByfGLISWpGVOGBbZyysnEhgoycorSr/GnJjf7OojeNvn3id8ixLEM6yrQCx1n9tFLxbbqh0aUgeJBI12UckUfg2pKzBSlTD2C+twRoJogUEDtcSRAYdtVwG4cP8BdJa4u4tSIqpXxN2i/AFn7AzbNyIcGoAfOCEll8+QBQzqqYHmc/Rqeusgc1KAcN4KDLF65oJORKlytMgjCybUdIh4SMQhcAXrEClK27EVzDDNjUwO2gTUC/O7EjrxAwjO9LdyLko3VxBTZSW1UVtLGFpG0DqWjpTpJC4ukIwEwTeYSbLLlz2nSBxaFtAaIUKrodvpQSe4AhlG4izxCaY4LqtpxP7qcEL5rgW2HUzJHfJKfASsVTVADCowtkAJCAWkh1ytAaMHu1h/4tJUwkHGRVkkDtZr43i40shnM4JRbJEybXXppS5IlxoRMLrYXxWQ6W9poxrUP6pZ+iX9NG7akjbKeQE32DDnulQ6CQJl7VrXrK3XlvWGAB00VUz+xnrs+kabV9E16IPno4oqFd+rIHk2WRALfKldZkJYn3q0tP1+1Rfm2wM/1IWqFz7i3qpch1piBqGwNhIzsjoPlsF3G11ukSJ4b3x0CM4+bRsctWl1PryELdmXlQUJFDJpWs6nCbOdcvmfueTQ2iyi6oABcqIBSBirUikjmITgqWNanwOr5jgHxe+8XYgEbwlMv6t8fC5xQ2fglppQjmCIDowCJshfueLdOH7IiaZyGUxziWHHldfnI8xSYQd5eWJ13sxr1hlQwSO6v8/GltrXOg1XBMsZzLx7yIiH/65FqvCiYU2LhnVYMFyExfl5mH3kFhjyi7BjxzbododCmX5iXrFmw+hNH4yR3lQz9Bl+Xv+yc+HIQBwWcbWTAxguQAbOJQs2CKT5VwOi8c+kJvjm2669k5GQfI4kZacGdbmdaxtGkFcDtv7mq4KObf1w2ZtZfa7RxoC+xdfcEnDOO+ba6i1eZvLxzWctlbeCMdJEMcftWFb89pl0VemjC4k1rrQHQgD0AMJmGApMYnioEz5VULsczM7Pynu5QEhqNI+p0kJtQ9AXzzrlaCKOQWPMiN2wj2aNviycP8reb3Sm9VECUgtx6PNWLLfEoe8vVX/zvvfQmCS3RF0wqCtRrLeP3cjDlvtQEAdEpG1Un73U1pAf1PjA+1YylTdc23PpZGnjDajN3C9/qy3Et1RliipilBDGVS0wxgXHoaGh2H5pV9mravY97zi+cMK29Y1uZpMTRVL2pDlRg64IlwGILMjcTsSDJYCHgeSWbBxfEllb235wv8DLOH2slz9tdYE4HAZUV7ZvOX/7C827filu7jPkzQswIAche1k8EWJeyEFb/elGsuF8+7iWohfX9mH7IfFP/TsRCsTfLuJKR6IVlIhiE4ZY8Bpog7S5aQeNI2dR/4VrkGS1rUVRZQcDS16H26VcNSrFOU+lxWpX/vBJX7f3Y03GA6PtLd+p/2KR5d7zy5c5nO41qnFLV0bQcb79BUO0S9u1A1p33pCbZ5tLKxKCTITgDOFVRj3HT130TbxYuYhuFzVtF3sYYABwfOOXW2pyXFFvs6XGb5+ZJQXxgG3Xt2KlvR2BC23cX7wlWsrSa3q56K7jdu1zfTEu8BTvFUDIBHMDKm4JXDdPDGeNtXkzhvbWi7u5M4G/3MAXm/jtmu+YMqlnbkmic4MeNHBDmHtBIFGKUKkKSvc2hrfwd/Iqdl0r4gyWHqkLA/qaddrnRZZknXb4dsCDyzWrNMQDzYlqq8o3BqRVqfRIY2A+Jf1zZftJBhV43N6wW4aVO4ClWVQ7kTWQMBJDk4Es135qZ8CjtnuZGGhRo+R5qRY7t3T9zfeTkyW+I0RQ1y4RSJXcwvnHVOkGs4psodAdluwvKi65ZNGQxb0W1DXXIEwdVK69vQf1ni++430fgR9jxoCRjIErxJAxVUV6KIhESUubcq3dGQR7S5gle31ws1VUGjsBsJzO13BFt9YFLUzDtXwVzMFIkNa6fQt+WslU5UNpLKCCSpBymE3DXD1YVZWCKbtWnViOSCwKO35hGZzONfvlkdu3/AbzTpu0U+TyrbW5lv4MvjxEpqTGtMYCAceF0YYoZ4AOdmxgnBxaI7dZ+6SSemehMgDRMxwqiapFUkFqr6zRZZWWLTpoVJVxcwcDC9gv16y5WTQTHy29gBKLExNSYGfvmXkviJLNCqSeOmGPM8qegjTa3KmMXQSPKhZHMlamUHvYSUN1aSWONPm59BhwMbVB4pd+iQkzZGQE2EBSaHS2rUfFILyGV9fcAEXTMlyQSDqO44BUgvpYRc69ABJsMdYIiaSGcoKX4J6QiM8V/WXHATS4ehucB5hgsa6kEg143lZrQRxL2Rd21gYyYDeYUNvIfRBBZe24PJ4vSQ2twWVeroRMaczdcqk9tSuT5/3ACRgMeN8cyFJXxFnfUr4FMR7MZepu1tcsG1224q+lzVSCRPgmSo8ubjPoKHxAxLGW4chSDa0QIUSqgK9VbXPLaTXZlRP6MuXa7rulygzkcjlOWLuhQNxByl6GAW4JlbAkBJdLWPusSct5/5VpRc+UUxsEGvaq3qtrUatsHfKduXnbXLxsDNlqDWCWHPEgCdLCkdzgSSpabLbkAAZyucYT11LWQwlkL4TilUiEOphAeN93R4xEDLvGwDgKv/YqZVgdqyR0uxbiqCHJyIbEa9Gx28cAjRENLpcL5+hcyAqWxpCtL0mucq6bbKutjjSettlZyu4qfzWI0uVsO86g93G3aRrrQMjQN29tNkKB/qlzAmmoUn6JSNLiR3OcTbQc8EgbG4DfMsIldATvAZZdr0nQG1gP+OJb/pDtOiyQRcVidYs37hBx1P0UUUqcsiOcq5IwLJeMez+4OxHRBtO76aQ7/OfO1N/w7xbSzBKTlshZe6PekjhSCIofQFktfXdWVliJUL5cRJWXTx2A3f3soa29gvn7gukTDNjcGLgfZDnP1qBxNgZY/kg9gLJQszo/6AE/jcSxhPrU0lwuNfL+8p8+L9qoMHWAM2r0iEDYA8Lh1RXFADFC2umMdpPPNx8tdUUpUDRvpUNjaPbBwDItdcjEthn7+m5EiSW6rtLKtT+xf7b80S61LHGUryVhoQuS0n4xvfGVzJniTP0g7JCTmEQJEm0Na3u0dSZWBQMYsm/81ZxgB+2FlrpaFI0liDgOubWPyzYGMFCx9FKvpx1nmnKm32MC8R3RZ+tO5iLWd713Rlh5yHRtUe1AuCBYkQ9pf0x6GxoSp6m1wyAGCkwIS89hJj1arqb0WOKydkjGFIt5LW61SoyW952BiXN1k6c4yOAUuZWxT2uvPNI1VSzV4do8V23X0jbIUle7Vn1YdJK1nVCtfNO3/dHQJRolB97mez5rjgiC1CwfN/DgVXfq6AHj9ZQc0eX/JKzMWEzAxAA9LP3NMOO/nW8bnxvM3ZYBdOxHyWAMYvrGO/kf+d/LE0cuUJtSpJ3OUznefWjJosQxvqe4owJBFx11l6zta7dNvdJr2cXDwwI0FMz3wZCBrcVn7+rELdU91/hRAi9XrUpzy3vzToaAaXsma0se7JZNDUhkka/luUL1erGbVgbgE5yi+xbMPeVqRrHm2CYt5wx5GcRIiy4tgGlhSYfB8BJMU6t5ne2vN+eiRN7ih8tVPNdWF5Qr4vzts26leY63RTV3pbbkIGNQOEvLhpr4Ft+waafB4PKy/J3b4C1AXI5evF96AparoCFBtH8u62tBMktUlOpSP3r0pavLvASKuIx1aYXg4tLClSXd+Equn2UlonoFkGwx74SIHDg3YHtrptY1HmZIwuNrISRYmnnmEse10kRWxN5wOheu9flyqMIDSBxmKZVTsBPKB2k/SJVvuR1omP2XuP6gN1vqGsDouM/CQiM7dg+qJa1CWH0L9yyQZXkqAeXZ7NWqsuwZaH9UNlEAGAlTK4GkCSjQMdf5c1YY97tnY8pmG39XAJI8g/JTmTl18cW29qAB2lyNhVZubSHSVkd5Ndfb8j9KZG7vnhuoOTjIhFDWTvbMJbaooSXK65hsPL0/MBYr/9tiTfl70GXUIdOp8hksV5Nfhqf7NKi2yzX+aOeIrUSREUv+DhEHEkanAx3audahEnyvikSinUTpUYu9EhdnK8m7WDHHd+CbnLIxrIJz+7QbRXVw3j7aAbYnTRZIrC4zKjTlkR2yxYGlYdt6tfGww369gLF5tyEF8lLQNoFZjjOfl0hS10MWbqW1OmYy3eWDFD00iWMAcrm2d5tcIx+UVRNg8NXC3XyGvC2F9Km3T9pURmHPPm9MOQwkPI+6qZdwaSSETqcb36G/g6u3MoNRb5eumzCSVyR9OhVzsE6lLM4NxKc2ZGXCqDlPree2+IGkSo3piCDHOLnhisq8a9MijKo8EEdbHupYWlW3Zh2tNkf5u6X9yPUNokfdwLXXk0niDK95HJoxJVsH4RcGITMYTMv1tohqbdyjfE63liG4gVcbd7PXMnVlj7VtaL4jiJqSeTNRKJKQK7CWDpCK1YFkkzDRaHod9nBJMoG6n9acwlPM4TzUrSgfrVXTWked9TsxCCa+8B1g2qsBkudMiCN/DXI1WgBi4NKOmIczP+X6Rts7vlkOmjjXwgcGvpdpoa5lYdvLm85WDpkqq2EpydTgdyhY0VY++PJZ5e3P3QB8V1PITEyDYgAeHHJLUz4faJJ2bvDrDalT/NahiuHtDVchDi0ID5UIHao4Jah28o4ljj5l2qhTLqeKV+S74Z9uII5ut0sShNQszTPWyipLpJX6+5jopp95z+JAPAy276AkmKWgtxyONN9pSoQ2KnRNO8k+bv3DtQutgjYOu6fZwrCLiJKtgyy91bZEbHtfp0NFeVOvtT2P6VXKFh4E8DNmECWjN6zHttfsRUnH3r6Vvy7GsIuk18d2+gHJMfNlS/4qJ3/U8gLGaOFZFj2K1xKKEviweoYEUlEiARfVjFxC5185nGonmSyB48JcpBCvRgeUUmL5bQ1pA7HLIAbNfh3OZRmba3tUEIcdsrrBi4eDeEhjclu7bplZKVEhcl682x2UByu7PAygTl/8xY01uXt6Hl8rCOiwIlxb3L2xKmfvpK+9e3fB3t27csAZgYfXw045A1IVPtk3InrpmYROswlRU5bwksDiX07HZNSrPqtClTTEAkpsmKoSCaJS2DW5pfawhWPimSQVzWOd4G8LFGjgYGlJneOCMgLXcP8nJwSA7guXXh7W1eo5dW39Kd+DJaWFW7YHfmDZ3P/hcwmSCrmBFZZIZhvyWcPLdDAzHPmet+rNMpclRq0y1udTfZ+58EPwnW9+NX/ZNL1x0xb44Mf/rXg3XOQEQkpJuVWJXCQSLjLqNuEambgTIoEoUbxFfJeIO+1TMBXomoAeAmN5uiKXSzq0lpmamoIrLr8MNm3ZCqeccVaTd7g8aNEKmlSsgC8okUDqd8lp4PCJY5BENI8avwcWerBXwQRcwyaGXIJYbpC2G2o51/S0+mZvfRtJtxCTa1TyIEcaZzNJJGeTIRgpiX886/znwhOe9HS696ObroPPfvIj8KQnPwOe/NRn0NgmVqwI3Nv0xWkjpj2DfG1TOsgebQtrL//2WjYCuYZEYViWYuVlwlwau29pUK7pqUl4z1+9A57y9F+FU05/JJRIbDpo2LEDq67mod86xix3SE54Uk2W0MINHneDwbbA0Il6NbCO4kkWaTKwbC5jadhxfJb58J8NG6RuZHD3g9syAE3cx+edadHWUleKegbfaFzN+fbxzVhHuLd9+/FxHHv33EtPNm/eDKeedpoYy0m9vOgbX4Mbb7gWVqxYCU992jPguON3UEt7gor2ja9/FU477XSYDMh35eWXhjIr4NkXPI++P//v/wp79uyCU049HZ72tPMiB/74Rz4C2487HsbGxuGbF30dJsbH4Vm/dj5s2bIl6/o111wNX/3KV2DlyhVwxpmnw+N+5Vxqd3JqGj77mc/BiSeeGKTCdCh3DbzlLW+BlatW0TuXXHwxHJqchNNOPz20fRqsW78Rbr31ltDXr1G9t9/6c/jkRz8Az37ui2B3GMOVl14Mj/6VJ8DxO06kPl5x6SVw+y0/h2c//8U05i987pP03ilnPDL8/gRc8LyXwPE7T6S+fOMrX4Ibrr0aVqxcCU975q/DCTtPytQvhblbFlmaEziQoNpeWYafJtT3g/thCYnGkA7uSZTOP7t6/Fhud6QFJhsbFDm27eWSuGw0zNZyGfnqnch9lzYkTXnLqxNLjnFPrG7xPVWT0MXa7aRV7//9x38Il136PThhx46AjJPwhX//HPz2698IFzz3+bDvgT3wyQs/ElS1r8Pk9CRs2bwFbrnlZvj+FZdn/fzWN74OV11xGfzZO/+KDPJPfuIj1JXxiRWwYeNGuOP22+Di71wE7/zLdxPSY5fe976/gX/73Gcj0Xz605+CV7/6VeHzSjh44AC8//3vD4SzEiYDIeD12te8Fj75qU/CB8J9fGfL1q3w5f/4D9i8ZTO8/4MfD8S8OxDw96gsEsVFX/sPeMozfpWI5RMf+RdSu4474USCwxWXfge++dUvw1Of8WtEIJ8PBDId2vEf+Wf6PjUQChLIW173KrjhuqsDYZ0UYHMI/v2zn4DX/c4fwAUveEmaD5nGVga4xGWRvsCCQsIsLT30O7XvlimtdaYE2MlNnNRd2hRKOUwJqfiMBT60pC+xQyzyy+QOjJQeIrWlVbTi8pAZ5+anqbCp1Zh2yoozj5EZti/q4HFwiAfve1VgQCSQofD51kVfhcsDcbzq1a+Bj37sQvrs3LkDPvbRD8Fw8DBhOXxn54k74RsXfQs++vEL4ZnPfBbsDlLjmc96Fvzb579In5WrVsINN1wHQ0OdsL7RoUlCCfMP//QBeN/f/Qv86Tv/Gqanp+GD7/8n6sf1118H//rZz8J5z3wmfP4LoY5//3eSUh/84Idh//6DFEKPg1oxMQH/+A//AF/4/Bdg44YNcM/d98DjHvc4+NJ//Cf88z+/H17z2t+EXbt2waWXfhce/ZjHwl/937+n+h997uPgo5/9MmzcvLUEOEBzmuhC5vCyV/5P+OLXL4PHnPsElhyBOF726v8J//Kxz8H7P/6vRGQf++A/mrlQfIDIhHT+YnYYaL/8gN85ereo+ZAUX/t+jjttNdsK9KQqPYyoLt4IRrqve6BhCdxA3+holZwP18Tq1BEwRo4t5wpG0BQhye+RKouHc0Ke96jhuvC5B6QhVmOfC2KWNjnxAuZpdUQcWP2RW7fAjQFpscDWwJlvveWW4AXbTV4mfGfnzp20boG14HO894gzH0F14LVjx0647tpriKBqGdhEUJs2B06/sNCDhz3sFNi4cRPcfvvt9Ozyyy7l907YAVdffTUR8gknnBDquDaoUNfCzvAb2zo9qIOnnfJwchpgxvK3vvWtMDMzAxdf/B0yyHftuhfK4etFSGQRtAVvvGVi4Xr2814sIA+q1Vf/g36jWnbDtT+kClG9uvy734Hd994LmwMcohIgNgh4GOiRbOj7RV9BZj5VoHYBNMbVxCjz3C2l6mlflMRqs1ioY5F1EMajfhabwl4aF8Vf4hLNduIwG71Nepy3A9d3rSuRu5ZVrMQTiaiQ3aWumbRgn9SsKOE8uKL/fMgph3hMTU/RvT//sz+F8tqzZ7c5YcqcQ25ksHqgFMi8Il5HHuHkXXyOBPKjH91I/ULkxut973tv5L7KjCYPHaQFRiyHbuLe4mIk73/7/Ofhox/9GFWMRDmF6ldGAQnOHBjpoRSvvo3bFTCyXPhP/vBN0Ly8WT8TRumTdy1WGYnHOoLb0TvhSKK6JXe1LnO5Je5UcVXWnoAFkRbwu4s+SOt6xKtWL4buJ5DcUHHXQdmqwe2M4dM9g8rOElSisIyXGGL0toF47E8mlopu+Az0SUUDKCqMSIs/Vgb9G69vffs7pO9nhQDIOE6Dsu0L13HmnnxyN2Kqcu/ePTARbBIQ9Quvv//7f4Azzjg9SIdeIIQF6PV7BJ+9e/YKkuM6So9cxZNBRfu7v/1b2BGkGaqCGOby1a/+J/zpO96RhlkMuylZa2jo6FbNTf52sk3w/he+ERwTK1dl40nh9gp35f6QuexzA1jLp1y9pSMn48yGaR/OlbAtjXzpdREXtae0DVfecl6y99b5x8k3nbFAh5MwUBkEwo0oHY6kxAHV8XOureXid63Gch1nMW02snaCj23RUZ7yzMmH3w+qoO/Lb37PqSEufau0v2D71rwe9/jH0fcHP/ABKeWJKD7w/g/kBRNjjlK1llXz2ifEtLg2HaTEnj176PdV37+CCORRj3o0/X3uuY+l788FI505pYc9QaX7QpAQqd/STrAT8XPo0CF6gkSmsLv22mtSFw3XR49blCIysttu/RnBZzpIqBuDbcFXDbmEkUEE5nn8CTvp1oUf+ucIa1S1Pv6hf0pzpjjizU5KqSP7W+dfpbov8aTO8EXYD5TX4ZEKLEcWESbgXCGlEpl11WpvII9KEVk5dj5VyAtFBgtAjSjXTBTdIGmXSxItkimxsTB9qqwtSCqX4Uppj4yPHIHe9YzEDfXP/ESD+9qg93/mM5+G7373ErqNRu/pZ5wBr6lfM4iuIgrXvlADzaBRjfqt//HqoFpthNtuuy0g9gQ8/4UvpWdokL/61a+GD3/4Q8E9/DTYvGkT3BzsHvz+jQvOB6ul8MndAJs2b4RTTz2VDPzn/sYFweifiriNHia8Nm3cHBZAN8P3L/suvPwFvwrv+pt/gUcF431T8L598d8+DVdedgn1iyUYMkRhNtoa/ebGX/qK18JtwRWMHq7Lg5sYn+8JsHnqeb8KUbNwMq8ASVJ4kA1haW+FbhDzqWCcFo16i/dSUBoc7tWCxY23MzIohFRWTvCxs/7Yx75dOaz3PuM26iY9/SFHwVFb1oCG4VWRu8ewPBm4cmrDsU19+T2GlFKyy/QCrsMZSaJ/O9Ltk/Sib6/STb5Vupm2yX4Id9etW0eIeWQwLjmKlrkHeoV2BM/V0PAwbNu2DV76spfB/3rd6yJEUfU648wzg1t1S+wVGt+nnXEmrQ0Qc6jQ03ViKMehKyiRNoU1l7f+8Z/A3Ox8qP9keP0b30LrMGj7DA114RGPeET4nAEjI8O0DnLeM54Kb37zG+hvxCr8Pv20U+GYbUczpwsq1XnnnQdbjzyKbKMnP+Vp8MY3vokWPI86+ujw2UbS7JxHP54Q8uhjtsGZjzwneMJWBA/XrwR1coJ+v/CFL4bHP+HJoX9htT04AYaHhmmsJz/kYYEAT4ekEQA8/olPITfyiomVoR/b4QUveTm84MUvz+K+ojSIl49MNc6jnWMp6yR+zFkNwUGmkokB10DwQRd3hWPfKDgU+MRaDenRxfDa10atsk35WI876Yn/nx/UkL78it94FDzmzOMzwqg0SC6qHU7cevqyfiW9Mz2QATt5XnQSwBl7R8RxFIcGCpbFas2OpYYWQWSJZ6Bi9Gxwv44Md2F4eIgQtNupsjPHdfO+jSyKmRJlolTFqsGnnLvgKF8WEiEdnBvUk3POeiRsCtLgY5/4LMzPLwYbgyNucZKGQx/GRoe5H110FmC0b49tjRrTCPkETzMewMMoq6FA3EO8D8WxltwL6vDiYg8W8Gw+0lIq4dgcdlwRY6s5HVE8jRilOYexeGtfmcmIM2GlNdiiDnJcdskGa51XiM91C0wlTFffd7JdACoZnx7JC8sTSB7/1qfvyvO613CYewwMxS0G5Pio+7FfeQCnEnLAFz0pNMElhZrEgas+Se4wlQJ1qitTgXz6ikD3lJs2yQcnel8FuTvPGkkidulnDZl3I7aXh8XQHW88VkJPlWdEUGnlTTdTrw1xyFgZRJIAOe7MEzVBiNuZnjuwwzmMCc24iewrISnYISnJyRp9tHe40Q5JEVrgrCRGyxCQF65OGR/pvAtGEMrrSKpTTfd0DzufB11FeCamEzkcj9IwAgZsMTLLqwQQPL9QatgM1cqq2p6JWIhV1XRvtJP/6iW9hTw6OX9Cqnhl11oSpnZ1MS2+qXgvOWnZVusJhfmIoNkaRpwgyYSusFUiMVar10weomMqKTAdGpchQMalElX7NISCwyY0B7COpTRoRWgPTdeJ1BdFsy4qInerQH3mcS868NTx6rEE9iXBSM29Oiw8jo6Pw8Ar47pcNxMJzoOuQUEaM0U8d0A3aWmy8b7L54KDLFktxd9hRNCh8eAaVx8ayWpl0nQ8QhV8Lwp+Z6AIGdbbGKjIg8kdXscTdpNuITBDpiX0rTBjFawCZ85196bVHMEP/4qkkL3si2/tmxflhAt3+3U/SggqLt6DSDjg5TxqsUkgxWfFVVKvK6Y+Ii3DV0QX+IxbEmeLaowA3iVhpfPDksa6EGW4QkFJvVPgKgfzqSIPZoJ8BhBmdDkJMcfuZ7l2wXC9hs7qtZ8gdk6q69WveS3MhwXC+YVFaE6Nj44NnhjD23B3IdQR3mlumLE4kiIOUiCps2wjjTYirURHAEuQhObaJksRZ9VgwSiXd7rEMkb2OvUg3jdHfJesiB+xRHfxiGohznhMnc8lDzSJo824huKdVNY3Xm7mYGjWEdZBLIEoguehJeBL5DISIf7tk6DA34LtGYi8UaG8cnGVIfqv9WYYotBvlzWcXdmiolMOpHX6BjR9Ww0+uZ0JaJJflzY56YH2pJLohBqpAcZ2am0oJ9BcQop0AOa8mNDB2kTpNc5aElHXQaM9Li9Irm5yKB0o0nn9qTadb3ROxgUZRlrCcQX86O9KZi/X4CM7d8HmcrUhErWXwMdueDDcH5qXtwVaLqt3mH8gZ4mKk4ZBmd9yRqGdVg/Wg5EGDlGd4GqVoOy0K4FBlDLNodr9FfJtOF2Ejr4bkR2iJPIGERmJtT6AjIjjeLh0VUA5M38ME0B9vcZYtNoL9+U6nCEDV0GDfPNxQga/rN3GewAqe5yx2yA6KAAyZcOqRy0tZIgRh6djS+NMYhsMcYg0NL1zYMpFhIKWMRhFShBSx+PMc4IfceyaiII9o+XekyVxH4qiy15q++QMJRGHHyCKusi11L7wsu9AeXfkXGqwJZnNvDm66nL3cOIuaZJ5ou3UMSFGNcdbuQFJWNC3MwSnr1sUEO+ajCM36GNnloSlJQ8fjzSQfjvDcaW/Hc/eoYzzyARYu0GfNH572z2dNUZSmhPDQPIeaksp+tQVLfgCRjqPHiyRgPRV76W3UxyctOF8zMkVV5k9DEA2MySXnBj6j/5mOQgxwTd4KKnt//FKI0rw8cWT5lUSS1e5BXNYCXvHbzwIURykjY5727hvn0yaaEMskR3gP3osFvKNWrizlhFAy2EaWc7ZQu1LMZJJIrEHpI6pbpy3CJwDIlVliFZUEs1jxX2uMvxUl29HxSqYCbBVxQnJYWP/0numYwI7V7wjOrIypcJL5hs1Ccb5vA0fx5usBgKRwlufOxc1Kp4WwyDMu9kKtCmjeOUMoaRy0hMny4Nx8v9bKSS7vAGQDjfr0gB7qcs+frMarIuG5ArU5GPp5aSMGe4UJUJ6HlUCnxOJFaNOj9stRLjCT4MtNRo1SaA0FBZQRl1zSiRQgghaAWYeW/uDD8bp8x50fIjhuR0mjLrmtROEmSWS1Er+rxvUptwscLioi2ETA0kN0qX3fapHHSUekiTzuYsizrS369cQkcfZDhCN2SOTZc1EGasz9gkkKRqJyjnL9xpQEpZkPHkV/L8TSpP9OKMTqmbZtr/emfK0J72SH5VTN1pFCKInflomVIrxCBijeikTVVKx3CZxFwfWL+3TS3lDSndxbE0My1U7D8ll7cH2OLZjRuB9IvqofkhckRJJJBBVPdHFG1ystADl0gJTXrMRIB5KTU/ZOPBqruf0QOhObkmLpe7mWB9ApsIl34qPdXqfnllGFpumKeOFRK8YA1YNSazSCm92v6otVgkDUwanLu/8m951VkMAUOcMEwbIbwdVNlsAbfzk8K42AvMQ7TyQHAFa2hl1tTDWY+pRAwXWgX0tdkm7+ytXe5IEiOaKDjJTBRIX0i/LwWKVprxF8wxgmbrFheQoQGD7yB0mgMXd6pseLEVgNvI5Pgk9L506PVeunUtBABggsZQUNdsipQWSFd3aV5m0hZaqomvc828MKO33+zGYUdeiUntglnyUKXBffUx2DXGB1Zn6WdUWW0uXvYHvk3oUv4XhOYjEIixayhtmqGo32bvMGFDN7gjRDSYOD4cvWfK325SJOJQlDHS8GllN7NqGi5zYtmbUCdVl1Rku3cmJwxBFMfs2aNEVLTVT+9s3AcAZlcKZ+KuMUEpp1hyPcmOOylWp4WPSN86lC6y61bVkSORPH1k+eroqacHn9XowBnHRKLaH+z36vZoCBeuOBxdPGYYlLiN3sQ0MmcC8Wz3Ov0VhFl4XC7U5H98sv0lbcNz/ShaH09qOOG8c44ITo53alvCbFAbihFhk1kRl8gBQ8g4f1VIK1oGYhdKBQRMDBFf8vezlDq9INl8++9arcU66/ILIWV2zXtU4fKYHV2DNdQaOCE3DSUwtsSX6dqVCZMbgmuVBEVK8b1iEg0KYWHwrKy7ajlJAkL6fMiD2iThSuh4lIkLquiZiCT+oPmdVFNVrlbi0VUUYL6cY1Rw/Ff6lequ6klfd4fNJUc+0T33N3ijbGVPIkIzT6QzJmDQmy0ucFnBYihIIMQvXoUNJIw3EOp0QCYg6VcVV84jlkVhsp0E0B/VqupyJtAmKwxYeJQuwV0FwhYo16Oqm8I7EbzUqd6Bt4Cw3EG6jVBP5tIkjAFeoWpDC27M+u6zDBJeB3CNRfE7WNRhHr3BJyOJ+fDRiVXLIrj1SVzCVJ8RcVl5PhxLwIPIg8bg+Yzkhe5UcDVVklyrhoJCcjBDI6ZHrY72UScbUkYo7iGpvWZVKKOl7n/bvcA6uBBMjaVgtgLSOpU0wa8NQFPxUMtC6VlcyDRA4UpodN+ryVe6fptlFovBmCNmViRJIoQfOzncGroHEUdIdNKHUKGE70Wakl1dMXp02rCeD1SKTcmxvmIOHQZcRuSpuoUR6aNYSFz70aSZzU3mdIJe4GURC1dny5h1oTT+EH1VJen3+9OWDCIIfb2ZauX8PYYLEBE64q/TUSRohjT+qLZFUrKaJK53b5oeI4DHkwlkmIeOLO9wSHNQoj8mrffooHJK/QubSjD26BoWdVDgatUMiQTnePFex0spqJnCQpG5iEyKJue3U3vCFZIiAt/NtHwygAtVW2h9lJDEYH9veTIRhYd6iYtXZS/ImKKEoz8vGIIRi20vCHExZl1Omy7oXgaVxN6zjYpsqfSBXW7jG+L7OcfKs5ERhx0RlPGSiT0M5+rUQBnJ1IQxG3hiETf/x6k3FG/BA7B4nTx0DGEPZcRGx0u5ECeCE2yZk5WTsvJZMiGcITTmReshcoaYS+uHPugYwQaQKFJ9PT/ylc6p2Feg3eJES/JBu1dJ2rVLRge7mROOdwkUkwtvJ3nzsn+yxk6p8C+Z6w+gTfrSSyAC6yR95aJMnZZP5m+1So7lQaKgpr088WC3eothaC1dOvnH5LuwOD94QoIS0eLFW5D0Xf1eRK2WCxgHYAxtdGnOzq/KsBEWiE9G1AXNSSwZ3r/YURBVPx6JqF+GlTcwg0iN5FwrHQwsstB8KN5W8SmyVIJx+KzHG97wa9mzsqgSw6nKEtS9IxkXaBTC2pxPpFuO5tK815yMGOeOEFpHRw1UxqfPZ8MIMfD7PsU3D6lOokl0ATfaXK+Az6Eq2DECTQYIZbzulNQMWXUYk7Ua6011eEHV12xZz43YepY16ndgkBqJawMDj1Wrac+6YG1aeOTSHlKvPWg+Ccab9ss1CwoFOfBMoFoRefzj1vMnWHZfUgzhtzsWFrwxWXt5U7omH4njTX7ASU9ryiXx1lORkoB1vQhxV2u1YkQrjjDqgfWDjfBF3SPU4WhdtIyebb0p7w6rOijOG3GIJiFzfG17oWVpRkGGfYYRSBBdNw6eu+lFCR9vVG2boc97Kto8hFIFD1t8B+G7HYxlGg0s23jB3CqbbRgdxHYTdbjICnPA6X3CCrEMtwMxbFq4nO9rEiIPoRsN/NRkE++2rCLVEHM7U5woAgvRD8C3OZSlkLWdqA5VybTpn0DEMHO5i86q2QBJPLr3NVfpCYuVcit2+LiKbxblGL1zK0dWR06b0QJ1EKIlgdHLZduJ9HpzoD+hb14BK13NsNXLt1Hdv5jOF8JjxKbLXEq+HG7pcLxBkXDkB11H1GCJxOCudHGQSAp+Ri1k5tzAbSEI4i6cE0y2RtYZIyhLLX4MieAsJIkE4PnGWRBxp4SyxAMg6ktG/DkgNVfGRqzPG14yE6lr1sgc58jkNwNLgIAKeYwNQ4qF04q3JmXfLtdxT4s5B55j1kw3KZwtqSJIzGA2t4zbYzmqhSMyY7xfs+o3C1iedPNoSAi/Hyew6cmZIG5HwLkIXbRTaU12x2teXdZuq1sU3WQsRlcf23UFiNZb/+qgepj5GhLXSRuwQtsXSuleliK4SKqpYLjIrHSsTR3L0OnECKEPMPKhNAZDmsGA2h0scS5Vvhrv7ZAvQn/HbbqsFw1GbV+TiyuHwf93UIyVclLH4Mel88A5Niq6b1PK7LiS96ofK0JtSLu9Uy+D12yXERCLpyJG//IbsKjST5KCUmAnB0KkTzxoUIlFkUhXVG6lseY1XDqpqlny4PoiSJUqSqorqZ1+8bJidnolHvGFoQ+HGQcNwVGIo3ESzVKgKotfxpteDTJR3qR0k7zGhS2raIEkIapIEIalOUp3TgarkYBXLeSkbtYRBcXTNOVSc0v47GOA1gzRd2pW8Jn5oXb6Zka4DtVwx2R0+4kNUrwZIkvSL64unTWVPtM46iez4jpOFLGBOq7zcG2BE1UtB5KN0yjmjZT2+tZ/6tFIJB8xBdXHT1bxd1ZvN9MmwhAgDfH9mZhruuv12+PFNN8J9e3ZTKh5Mq7N502bYsHEzHLP9OHjkWY+CuBahVA9GP9SPU+JNBIwfzH11y803w803/xwmQ92YzBoT3m3asgUefsoZoY3jYe26DYJsypgscbj4LHFugBt/9GP45re/BXvu20vZTs49+yx42hOfJMO0XsnETLzsQIyLGLUwPEG062+4Hm6/9Va49bZbKWulphjCZNs7dpxAqVS3btmcVCmBLXuzfT5zDlrR3kXRZqd2CQni8h+MLbqtuqlqqcHeNSfbQ7YeYvGswYk9JC+FKeeEqyQhAdaoxejYmEg6LkZqlj8fQ659pYjoEmE6dUNGP1fBM5J95KTPiXiaNK0Iop6hTtxNxWFzfQcQ99gbY9NWNjM9DV/6/OfpMz0zZdowPE7oaeOGjfDIcx4N5z3rObB+wybwusfFjkLVEp/g+5WvfBk++5nPUDb5tpFguU998hME3yc/7Znwwhe/CtaFtpx6oEgQC1F4gV5A7r337YF3v/d9cONNP8rqu/z7V8HHPv1ZePdf/CVs3Lg5b865qEKD2Jia9AGJ4Atf+Hf4/Of/nRjEcteOE06A5z/vufCsZ55nBuOh4f3zkOxMA944ryLN3HJiB0qpI+MxOMRaYKoYf3fWHnXW2wEgtzWijcB7mM885TjYdtSGpJ5AWgBNRFIgkVfJwH9QXXXKGq+EGREVXDTiNEo2clcx9lQR4JjSGuYXOvCz29fCj25dC7vvG6dna1fMidhmAotSUFQ/Otc8fLryTaqL03WKxLUrwS1SdxynjeHcSnzvvr174C2vfz18/8orYHFxwUg216oiICe9+ec/gx98/wo46+xHw8TESh6v2CydaGew/TY1OQW/+5Y3wWc/+xl44IF9LdU6O+t03X7rLfCtb34V1qw9Ao47bgfYUBOCG44pMJ/ZIPHe+Lu/B7ffcQcYVhE/U9MzRChPfurToTsyFsDYoRQ8/N0JDKzDv+Vz4403wBve8Ntw9dU/hIUAC9O74nLxyb59++B7l14Gu3bvgZ07dtKZJ7yYWkFK9aMfl9Vo/0rfBVOK9p6PanC3SrYdg84JkQC46D11knqX2+0ccfTZb88HoM2kZHKPOPX4SCC5GmOIQ9WnWIP41L2mnhR9VSJObR3KxZmjJyNP27dUrwRyaHoYPv7lEwNxHAG77puAX+5eEX6vg4NTw3Ds5oMw1K0jb/YR+QMQOlVGIB01fCFJFMptBUIYTpJcO7ELwu/7A3H83lt+l1KKNqZuACPT2yh1fngVEwkmnKvE42cP85menoTXvPqVQWrcYqp0eUX2MkSysLAAV115KTzslNMpu6KFWeWYSP713/4VrggEgE8w0+Pznv8iePFLXgEPe/jpcNvttwVpOA1T4TMUiOPhp55BxEAIi1sgJKsKEgkSy4UXfgze/e6/JCbR7F9kfcWDhEm33HJrIJRLKXEfEYmc1wKGQPLNWknDZcnC0iNTpBsE4qOTg8+kl1BWFhngwOXdAg79wSKdtUggLjWhOq/IIOrsmUIgAAC59wMgIjkYCeIz/QxSzlYjOaK2YjxALkmS3ECGaNgpZ//Qlx4aiGEEymvvvnE6FvmEIw8KsFLwJLlKO3JoZjdJkKjrQzJCtR0nBKKcHgH8P177m3RQTcnRtwS9+rxnnAfnP/vZlL3xiMDJbw26eFGMiOSuO2+HJz756XHs0WEQCrz3ve+mc0ZKwsDsjuef/xx49nOeC4993BMosTTaIpSDF3I15KabroMnPeU8GBoaEumRYPeev/kbOqcE6/yb9/4jPPbxT4INm4+E407YEeyZ0+FrcuTB7YFYfuP5LyHioA+YMPfw+8orL4e/e9+7DQFY5HFwfKjvrGB7HR8kBJ4psrC4GDPaW9ihdL3m2uvggudcIHCoMgKJ3oRCiij+pL+FwXuzTOFZhVcJgvPXlcR00zMzJAa6mIDPMLeZqVmCZgfPhvES/uA0FU8MPamANxT6ovkmE1PdzWVaHghRuNi29745HGdUBUGQRF9KcCAqE7t6f/7LtZE4Vq+Yh0eevJv+/uFPN9O9G27dAL9y2j0wMtzP+hk1NukLh607ynuV+m36SJKjkkOEeBAf/vCHYXcgjvJ65ateBa961atjLXw+uocXv/Tl8OY3/Q4d42YB9uMf3RA+NwaufRorjDjG0MyNN14LF8kRat6l/jzjGc+E17/hTZxhXQKfnvzUZ8I9994Df/++98AVl34368/e4Cz49kVfg1/79QuydpEw9uzdS38ed9wJsP34HSpfqKVjjz8Rjg3q2R2330K2xO49ezNbRAMV0Rnxnne9E6Dg3vgcj6Z76SteRQTNZ79XUWW5/rpr4V3vfDvs3rWL32RGTdLyAx/8ELz2N19r5sJeTdHpI7NuE6v2vYSX+tfc3FxwrswSsY+uG4llZ2enaV/N1PQCrB9ZI4vdkPQxJ5n7MK0li6ROPB8jNWj+ir5Cw0Vo1M6OREwV0gaZSxAVd8BJojBNpaMDSdsZnJVRJBH2PDARq37kyXvgzPDZccx+Iha85ha6cIAISFWr1C/yHciaQV8/fYmGrev4LEb81pCN6+tf+zqU1zOC1HjlK16Z3o9h5zUh11/99f+FNhXjzsChQbxN2BYGCH/h8/8KJX/YFFSl3/29P4KR0XFaNV9Y7MN8+Cws9oJBvgne/Ptvg42btzT6ddX3L42t6YVnHeY3k1UHAucJOZbBdCGamLX8/uSFH24xxj28KfTzN3/rdTAxFmwXDIv2/GEb1MOpp58J//KRT8EJO3ZCiUuYPFyPmsukRQQGZP3iUtZV482YmqWdrYVwMEiL2fmw2MopYbHAYoAr5ilGtRejrSsrFjTeqHJm86MDaOqQxeWMeJXfrDcq4K1bUD5IHGL0AWXxkOyxlHvKyYSoeZk+NeSgGg5S4sD0KHzqGydnKhcSSRxYbD9F5HLUbk35chcDgPAbAYIIaDcgMeHwAvK1QQ1okx4vf/kr00aqUFdf95YIoSGCP/yUUwEKON5xx21xMlERQPft96+4LJtW/Pzmb/126CP2rU8f7FtP+okbrkbHJkidKq87brsVwKAPE9smsjvw92233wo33Xg9WIju3bMLfnTjdfQbj1gg6WF7E8aD0vDbdLS2zz4veukr4UlPfioTBuVbC+sjdY9+u5oJBb9Xhvbf8c6/hhLpkTi+8p9fycafvkuF1qpXJW76FmzN1TOOm6tgPPQl7dXxMZPNyPAQ9INKWGUNRpu7RMO2xpQjJ+KInMhpQpfkkch+Rw+I3NNnGRGYFQ3n4v5p71we5xTeGx6u4XlPuxl2HH0gA5EvvSDSBhJhLyDvYiCSBeLIzJXxs6jfhIwJERHZyWguLrQ1Nm7aGCVPTJwsH13zYQLJr/v27o52B47z9oDQ3uKcXGed8xgm1j73ZVG+exKBjG0+DDOyFxcdjVDeDA085/zz459/+n/+OCD71+CmG66FL33hc/DW3//t+OzsRz82QtN+ffoTH260hQ6BF7345bKRJhGDo9CXHkuSWr9rWiM67fQzGvVcIsdPZAAYwJubC4PODrOltImmcBqAKqshYhdjcnPVOg4Ggu262ogfbxfz/ICm265kSIJPyJzuJ9WpLM8xTN6oYFLSp/fyelwxbMyU7uHozVPw4+DFSk0komC1rkN75oCkFEgybY47c9FIstWn1WZe2a5g166m9ECOrGslugBojUTd+oqcOBPxJSzARZUlKgr43ubNLIlUFbYx+y4hwvjEitjvZu2JF2M95//6s+Gib30r2Bd7qM33vucvoLw2btoCL3rJK8GZTJZqE+F58+X18FNOi0YxxWkJLAi+EvVKDAv1yIozSD7m3F8hm8ROKS6GoiRZsXpNuukFGJYAIJccDVJxS7F47g/OabfrAlNchO7wMOgaTK1z7yXURIkjvl7+9h7amzMIrX87vW9VLX1qn2tHIRuyfudBapZQqkyAqlRpRu6quifJkB07b0lahbIYphH3Zzk7hoRULIqpCnrzqWFd4NzHnJt1H8/6AONS5OUb60XhongYaHmNh7UQZQLIsY49bif82V/9bdb+xAQnv1ZniTKwEgOmpwp7IDw79rgTElR94rdY57v+8l3we3/4+3L6Va6z45km73zXe9lN7CLfo2+UcugAKNHhiU96GofBe47OBlnsZSYEoKHhXscW5gOlb3mRVy6sAWUEAgXvkjslkbTjqEZngNBFig7pYvRzrwedkWHoBaN9ZHwERrpV5Mszzttwd5Uc8CAunTQwSGs6HAVFLkmc8Vw5oYLyhKl8o5XLPwWoDk2Nwue+fhwtHOaXuIzte0ZdK6NWc7IDco2mua3h+OOPi6Ep+p4Sg+XSylDUuMTirOtDJkXOfvS52VgRIdFeAafvadBhXQ65cX3noq827iGBgC+YkASn4olXH/nIhXDFlVcGz9mNRCjjwTg/51GPgXOCasWSk1MbxQzt4fX79+xuad3DccceBxBTJikxeD52TXIbe03S4Nn2POH448MayBW8CCvrUbxQ2mlKhEabzvApJR8L3fxdnzFAnpPhoYrOY4G5GUowPjqM2457MHloOrp/uwBJHGZDLijFdgEaT9IvS9eJ6pu0HnsOhjCUgKKkgYaXzDd64cgGOWrTNNy9ZyISibWNcsISkaBl4mqzSC3LboyojUs8tutmpVrVrMiLxSuCf+4NyHfTTTfEfitscM2hsatNGIUrJ1vVDP2GJP72BqP5phubag+us6T3BWGp2hROdM7Z54RFy0dBSrABZDPQmg/+J9tpCT6hDnT/llwUjfmJ8TGI50gagFFITZ2CIFUFi1sKyFmjcGFHShpvu0zQAj6DUcK2vEz+t0q14YD5/UWWpjjWB8KayNRkWBwND9YesZbycczNz2OEsuV+liPqXzX4gggGiTIrR2jAHsD6oEAmiJf0fRTfEY9BFokqthnIxSzp/lW1ygLouJXgcajhaef+Eo7eNNXSIy2FDVQ5odBXIiD+0xtFTl0GcjSYGqC1RgXwx4lq4eKuPt7jUslelz/4g7fIOFPPn/iUZwRpsaUYiTMwtLA0SJMNjBERPUp799wLdnZQGuEaiyJEVG40gloOQXXBcC4/VSAQfIb71FGKkPXmOOvJtJyDaK8V6AnC8lgfyD51Vc3N3nWn7dZiuPfFy+XruBfEQY6PRlhn37m0aGfd9v3I3ISRgYT3kDF+4CD1DRdVF+YXgzdxmp5jtEXaMJVd3kgVGaThqrGMtqxfznbKDEUFROR6FeQPpbRQi65eJ5Tx+QCzng4iV2+7lAM2Si69UziTBRmiX87b8i5TWczdTHroKx+98MJ4yi2NJfwzMb4ieHxeARZoJQE1x6OSykE65csFyXEtfPrCD2bl8J8XxPohcXxCDPzNC7iiaPIJXFDH3lAYuvcJaeWkG0SimelJg5r876ZNGwIR9Q0sIf9ElVHHoTtWnXyEOcqOUqkIwBA2GDjH384tbRL4JMh1/Oo4WQwqFcJxcWEh0GiPFgdRenTCGsj+g1NhfeSBINy6xgbx4ikRUawH3EAMTYfMcI5E4PMOccc9QBFTFXNHyR7qeBaB+p4jQnuIgYs6stoSSd7mbb9YGWyQIXp/7/4xMLBIvgWX8+JBMFWJUYE5RtrL1lCtx6Xxa38hPYqAwN833HATfPwTnyjaxPWCV5CtYeKY9RFYwyhjCJGJpOgjlBrve8+fx1e17BOf/LRgND8VvAkINc0bYvTx8AEb08qIrTmzbHS3mU/TZbw6yjdUEsvWYRe1Jhe1BykGNkI72m0ycgdJWY9jK4SE0WLbC4DLC5rSpEIG42d+Zo7ujq8Yp3WPHrCKjoSC50rKEWxzQdyFFUzc+OI2xOnxmVPeYJyz051zkyhFfNL0lczJ7+yVSCC2kziiB43NUvcqE6aPJ5byeFP7t/1iFX3KiwnEG5HaKAEtN6MUqVRVcpDNBIepG+IQRmAzFuEHXah/8Z73xJL6zqODEXz++ReQt7PZB58TSWRSPpUVOwfjl976+68nj5K1ITdu3ATPf+GLA173ICFEScTJ0eDiHnwFgMJfRhK370KiBucg2V/C/WNerzR/zml9kOGFkniMQQPLwzwoyBsE0bxlmFUL62sQFEsPPfcFgzn6Yb2L4tUcBnoGm2NoDHqLPRgd6cJop8b9IIE4/MGwfhM+MA98smg36In7w9/j4bMGrByL81eqAzJQRpKENE6jeZ2IalfHSdIBVJRVRDlIJdy7MkTvgXfH+ZxVLnE1CNfcb/srC2ih8dbmd2o6pigCiBLXxTqEOPbugTf8/h9SHFNqx9H557/1P/9XthW1OZbcBrRdoP455q3ve/dfBON8dzZGXCF/x5/9JWxYt04IpDIGsPzrUlh3TB0aZ0wH4kwErSiPPtWiZeKFunxnSCkiEojuHfGuyfp1u4qv0iJxk10B5CpWO1MbhA1L7TbtBbVqLqiLI2Pj0OkGh2+Pd0V2go20dsUQjAZYYlREt569mNqsYUsA5/5g8O4Kgz0miJewcOIXQ6E5iMeRRemR3MH52PkPPS2IjoqOKhu+pVtpfRK1QBtcIapj5tShqMZF6cLgut+oUoOuvQ+MwTFh8VC7HKWhZU+GazHN83cl7SXpZtDZt3MxxfWp6emCOPhC4njPu94N6wKH78WXPPiGsuLNM1BRCEmx8vCh9/8DfP/Ky/L3QplXvvLVsGH9EUGfXuRZoLDuTmJcKhlIPKcYOC8IqtKCqpOXRKYnODnjaZIL94/4znCUICnrO4C3nphinLX0qc4IyYnWAIPo4bCvPE4LUvsykQvzwSHRWQzEscjJvwPMFvsLdDY9wm4hqF/dxUUUNUFiVNNBtEzBwlw/FLgXhvAM75Hx8ML9ob55SEgEibGYjqhM1f3Izichg4inJmC0E7O8Vor8oo4oLseTcJWAavje1dvgtl+uWRY4F199dBCTNTx854EWqRF1t+I+f3sjFSIjMCVkDpMQNcTxpj/8I9i9e28GH4w9+vP//bZAJBth0UMhdYyzw6d+ZOqk/g7/feZTH4cvf/HfIB+Sh+e94IXw+Mc/nk4k5noR6buSld36hmSeZD+Hdx2Bs8ts+VK2KsJPrGiqsxQ6Xw1FSZEnrk6LvqbDoIjKxKRWh0gzq5NlROIio1j28oX0Meohvo1nys/OzgQjfT5IkCHKkD8cXNUj3WFO5BEIZGhoJECwF5x5w1vJ9ba4cCgQRbDch4EoamY2rDAurjIqsIeM0wml2JxNleinHJzG0LZGOHsSxIsSublPEsO2IwkdlIB+cvt6uO5nm+Fwr+/88Cg4eusMrFnVz0WAACofCxhEhIS0CdpJXHozfgegWz7//l/eD7fednsOnnC9711/SYuMPR/Rjcfs4y7j1Gp8bCSISLTPfPpC+OynPw7l9fwXvgie99znM3F4CZSg4cm+erUGkyjhyAKK3K7i/g6VtNF0k/rVbMf3cN+ITwobXWgPeWSyZs1Kj0JIpA3mnZI47FOX7A+X4Kt2T1s2xHSZOV2GhoaGh2H1EesCYfTInQuShadmr37QoBbI9u0OdUdgavZOGKKHuK96JABtLFDQSLDwJ6hfdd2BHGQ+2SFONjo5DmHXPEfMoJn76wTzq954G70hBtN7S0AGqFfeeBQ8mAsXDa/58Xp40jl7QPfLK3diVUkn2pvxJZVGx5r65fMGJIYMa/nHD3wAvv6tb0t96dU/esub4PiwyuwNovM6AZeMTAFh6gSVpM5kpAN8+T8+30ocLwgG+fOf9wKRHAbG+Ltm5RUcZ0ehpRxKgCjbSiPhi5z2nD4o822IJFB+smnTVoACMhjmsic4CzZtOVLgIvaNdTNFJumiswDv7Ap2FGZl6SgOhXtbjzo6jiNXz2INMOhK6z2Q9dMXZXSfynB3lKrXJOZVB9c/Krj7F/eQGtpFEA6NBL21dyCImm2ho/PBcEEAdmB44a6gpx0BeXSvjx1XxCW/eQUxTT7ZFZqbyYlgdOpnqNlYj8DzRoqw7pLlVhIquWfP6uDOHYEHe+GW3CedsztDyoiYLhFIXILMMryUkkTf177z7ws//Rn4/Je+ZCaAr1e8+IXwtCc9MeGHTBW5jiXkI+Os3mccVyXst799EXz4g//UGNuv/dqz4QUveBGHkzs98bGQPDXHRvVFAjARyi5JcrendjkJdsoQr3vEK7FZ8OXjTzgh74Tg6o3XXwtPIQJxOQJHjUHsWLHeEQWuv/46ePMbfgvMVND1iU9+GnaedHLOaTIiaRcQxn3SeBI7q54shEEwzhfCeshw8GLhouFib4HURPQw7js0F/BtKhBIZwd06utg1douHX0Mrg8H922F8ZWHgphZFQjmfuh0Zrlp5QImNYseWsPSUoSxZ886C3ueIDWA+bmkBVKVw9tQjTQgp4AIDRyafvDEgRdKkYOT3aBm9UATOWTRu2AI3RBIGfKfQOyz35/89KfhE+FTXi970QvhZS98YVQvqQ5K6arwKTxH5jLmGdx+x63wt7StNb/OPudR8JrXvBY0/gnxvO90DkQKeE3rykiJmlSPmJmnWELH/4ASKuUlrmXdnZgTb5jzuDXZ87bdLZs3BVtqS5AYuxJgQvVXXP49eMrTn5VcuwxF7gNls+mrSOK97aEft97y88bYcRfizp07EwZYanBtyG+vdrM8wlWJwyNezJPkw7WQ6akZjhLr6dxD8ASuhCNWj4V1kPqu4B4cQTkCw6MLAYKzMLRiMuhoAZBrDsLkgRHZGeZjR61dEFddY0pK4LI1hxWAEAmJ+XI4dZJKulrtXNpW5VTsOlhG91z64oNh+hD99Nxk3hfQm0mitbkJ43yFH1/88n/AhZ/6lHmff7wkEMZLXvCCwIl0kVFgIwxEvXx1Kx+UnjiMAN4Db/2j3ytax62yx8Ob3vg7BF9NgMFE4qO7PDIdgVsfVZ6a0xvp+YssIcT6814ScydnCW2c66AXrEv2SuU7RNxPe9rT4eMf/3Dqr0cJcg1MTx6ixAucShQAMgLpRQlSyYa5b3ztK43R79ixI0LBZ6LC54IAAA4LIzJNIGkEiNJTc/ON5+mUL5QkYcyrV8/CfFgKmZ3eCPOz82E10VOCYtRFpye3BRiuoIx9VEnkhnJUFznGJAZHN8YgIGTDjJbTbIFsqyhhabiBj6EGkTDi6JQ4qpiq5b9yjQ0tUB/pg/FGnuONKok36gD3XeOTeJtoOg667YPJ1v7lg3mIB36e/IQnwouf/3w+JdfbI9E0YUU/xi1h5Ch/uA/0cfz9wH274A/+8Pdov0ZSAYMNsHEjvO2tb4UV46NSj9aZHBqV93F+nLYrXBxtFfTYoN8f3Zu98OkvzodvDLmQT1hR9uG+7/PHBXd/RTFai9TfC57zHIBi5Egc7/mrd0CKRNB5ZqmsUsRLLNvll34Xbr21uQHtmc98FqiylLOnxJ0dDCaOBkuLNlAiDuwSpncdCYb60FA32CFdGAu/R4N7d3x0FIaHh/h++FQP7J6GyYNhraP/i7BwMg5z812Y3N+D+3avg7m5sHQ4vwJ6/XHbReGgdcxP5aAfg9DiVktN7aN2iRCH5ppyRX0smR2oBhs5uqhhJ20/AP+Va9MR02HwixHxc4JOfdZAOg2uiwnuJCDRBidiapz3f+hDjbaOO/bYoFP/NmiKo7SzsB93F4JtOwb3Sb9E0t0XiON3/+D3YwxXHEsgjne9852weeN67qvu3MsCJb1hXl7aSB8eQ48XEoMHhwmhJ59F2SKLHyGM8HFIJHVgMjUSygKsGh+GC5797IR5gnpXXPZduPCjH4hEoiE7zvezQM89u++Ff/qHv20omJh58VnPepa506Is+Vwa5D1oIRwralQ5AIiRGSTN5UhvULUUFw1pG/YidKvgAx4d7kkDKB5nYaG3Kbh7D8JQ5z6YnVln2kriWw1IjX7VmJ0UVJG0sugHl85aT4QrRx4Hw4uKmlx7pLsA27dMwp27VsKDuc46eTdzUUhiO7acmyLSZytz02/t1p777oM/etv/Zt+/uXAV+02vfx3tWU+2kyV8SCqotO+kECK+voAu0zeHhUbNPGKvt/zOG4ggMCNIPImXO03Pa3XReg3KY1hv3CApm6K7FBL8MxxUzV+WB2tlcJ7OY6Rs/DUb6y9/0QvgsssvC0Qs/ZSKPv6RD8KVl30PfvsNvwOnnXo6KX21hMHvxr3sQfJ+6YtfIPipGqZ9ec1rXpNjvzNzU+hU+qf9BmglKUatgnLQ8VD3OFkDpn9inKwiHGN61o3Hb/B8+COKlZ2ko7qhAzAyGgrX06RHv+L5r4cnnHs2AY6SqYVBd4Pa1XXehIW3d1Bsb1BQpOcDhGQWriA2g9ghmJTh/V98mEnIsPS1emIOfvuCayJyZhIwtmX66o1z1+fKqarzr/qt1wXbYC88mGu5ha1v4MKfFHn7X7wLrvz+DwaW9ebfgc+Lx+/4k7fBwx9yctajQTXrpRvD0A7pSC4xzQ+mJHTr7XfA7731jynRHHMDIToDVwzKRGLGfFj4iQ4SUw7/fX5QS9/8pjexXYQI67omg6Mm3SgulzNZwjU5optwGrOViP2MeNsNLtwhPGIijGd2dhbue+B+GAkeLFwTQZUTkwqCJNrgcx9R20DjfCTcDKIT47KQ0kaCHjY03Anu31VhVR1XTodh0GxYbmk7qx1WwKVkC/JxjaoSNxSdvTa6Kw521fgsvPTpPyaJt9yFxPHSp94YY8FabYqoF9cCWKNW6W/Q/RP8/WCJg8e29H98Qm2fJhWRaKmyGSK36BjON9FfNhqbj65bpzSulfk7Mj2BG6qIuKBGH7Fd+sFWOfaYo+Cv/vTtlHc4uesB7CIEZkBBVXF6aiqpzZBUaPx+5nnPgDe/8fUQWagvxgkwwLhoe9RC/G38INwbHxsjL9bC/Dzr/ZQCKqhWuGiOBIMOhWCXhAJrwxsjMNxZgP7CPjiwL5iJvSE4cGBNGNgENAMTB3y39CJpxeJOVGIpfoMBn1cgCeISJyAi6cHG1YfgNc+8Fo7ZNNgmOeuke+A1z7oW1qyYkxptVseUaSR9EnEQoWQbfgwG+sGc+//lUhulrvvgD6eNFvyJ9+VyjU9OTfbvqq28jjvaU33iqD0lFPlsD0Tyl//nbXDOWY8sOjaok6mjK1dMwO8EtfRtf/T7bKsMescPVp34O5f2A8uZCyUi4hWmZ6q6nZjqCtUtdArRZqlgfnQXFzeFgd5Pq+djQzMwOT0TqOoA7N+3jSQYEgoU5gEYi8iDb0gNq8n6GAfkktvRFxUVBJbWTHzUGSwMSJI85QbYvW8F/GLPGjgoaySrJ+bhlBN2h3H08n5qG+UgjALbUMXNs6K3/+1XXdfNm/+NjVlu3RjUUpfq7korqjKY5xR+sn4dvO0Pfhd+cM218M2LvwtXXvWDBrF688dpp5wCZ5x+GrzwuRfAKnQLa5loIBpJFHknr2G4AgndYLJqv0w4DNoarDpW0AueO8xuwjFYQ3SSMapcbuvJ53ro7YWFMEfrxhZgzyFc9zgEnaEtgbJWAga8ve7VF8DjH3Mmid4ObsFEfY7sED2APkLL8CcjKeJn0ET4hsBMK9vAXCzWnoBuv3Mic6AI4SMQ1RjMqASiTr+cRG+0vPTlbZ0PAif17cafpg77Z1OlbaHsZS5b+vBGZ1+WGDzKdzxEG42GwvcNP/oJ2SaYpUQlNe5dP+20U2D1ylVkB2BIRzwYSDg3Z4/v8sfJN1TGNm3pfTRCfFwg5Txi4umrjQ3S5a22s3OzlKgPmTFmpEc7hGLIPUd/oAGPBNStesMBxUdDBWugXwUKCoPDXci+DquLi7MA3U1NoBQz5ItH9HGZszEFymX1eIiro0rYPrFv5igc/h53tkkjeVqgrAqwqELfMWQ9ER8U77b8hGylyml32xEvq6ulb+1vHCYiF8X4T58RiW/UffiXz/49rFhZ025iXZpvbDF8Hv7wh7PtKcShc00uVad5yrCGyhj1y7esmoQrNYGWqwEXZ386WJibg26QFqPB5kbiRPURCYXUeSAPBWAWIJgIrQ6N92BfoJqxMRdcu5qErCOr4S7voNOu+dRJMMQBcZnKSJG8h+pZ0j+SCE7hAi7uV8e/1OUrNVkG7WwvBoPZFX9FALJ8zwtHj5Zh21mxJSbTL4WifsBvaBcJBaVZBPbttQxo0ueAMlw3qr3OLVNf3kFv5LjES9BuPTonUThWtCeBV7jSScZ4SBF7p9hCtZaQaZH615SZHgbNgG8fu3zZdRAnSRvIPR7WerphwRCTWpNHCwshgQyt3g8Lk11y2a5cjT7hmSBy1lLCCUoPmjXanJJmR62xnVQu+yzVkFA6m3Tzh+4J4UM9qzwCuOiNa3Yj1e3yEhEnFHYSTq0QZLgWSGWQLEfVpSas/WlWtfmdJtSlPhR1LUsQ5aVj9wbRfGQ1wmyKEBzX1neXVypueE0tq/F1HlOi+jobFKE+xag68LKdUP9L2+PSMnE+wy3XEgA/HPhg19AQ18VCXEbF/MxdXD0PD/F4jIX5BeiOd/uwepuDu+4eB3+oTxnxhkf6MEvGuRcxaQG0fPNskJfSo+hg8VcS8GaynNgOKi6QOKo64wSunFFhQnEhzAGkNRDXELPUqpd/NPufT+pLiaD2zxRUyHCqLaJD3o43f+lXRHXXhKoDPxDabarVYc1MdJC4bMdeZHtGUqVjK0qpYhc9K5mjKo6fYrokMLE8+6+i/e+8AKxpIqzGka9rDGIo3kBoKaYESxIZ2zu8GDobpAaGluBedPZoOTqIiLbcOj8C9aFgtCwgUg8ZkQjg2me72QuXdyjrWDaKJtdPQ5VfzoPdaJNerjj6VI6ITjq4j93STToM67TASHeN9yIhk+HSEmoAdi0ECkk3gLurJPItgHIl8vumumAeJuQz42pDggE8feDlDSXF/jirIilC234lOZmeuJw4JBq4lsR/XgwRzntmUB0JyHEUcWSgRlpAIY1zqJgxRB7HuOABGip2O1Xk8CZ1PfR1bmEeRsN6CNq+uDcd1z/QgYDjGB4Zge6BfRWMruqJlMi5lkZfZnQS2zHD8pExJU4s4PUyI3Yggy9vmRcoqXhwZoYU0dX1m/qcNm7lOxyzvdjeEBJ4s9zBP2rKBCiTVqd42+SChBjCQZOrtGOeObOibPBSyiWkzHiztwjlE6HD8jDLicq8IX1WxPI+lo6tpB7kR15wf2w7JjxfPEo+ZucX3qJwIRhI5K5LlepWayeR1U5gZFfJkyAXCZMxV9fCcHMNxeJuO6Hwhesf2PbE2Dgt1Pb6vM6DdghH8vaJDXdXj3dgcj4BoXWhqiBNFYsJTZQ2ygkCQ0wuq8a1FWtp2CpdSS2ohHCSHaCqlHpKnJxZrulo9BCgKARURNvVX9RHa5f1lg5+Ua4n5TRFabaNzINBVMPfhRgtVC1alhKgEk6uZVwsVFo8UUaZSqp438hVg7wQz18x8bZSh8BZqnMlcToXx5i/JwRS25V+b2DsjC+mBnusHScHlPxbLp/PbKgOMkaTSxKA5GjwWX9LVIx4KJ5SZWCYPK4jhFHRAmGwyeuajozrdnoHYArWm2oGkZ0vQO+Lxsuiad0hcq0lKDrjtN4Yos7nEy7I6SNQ04tRSmiKUT7WFawqoXXGecys45qJy050LajqeL8ETae0zwuaTYOSIaXoW6VmoUkQ8b5LkqMyv/VZfIeA4XIEycq4oies7fNeDzx9wNFHjeqYpcRVwl9ctjPUmQa817M0wNCBSxKqNk5fl/FG3Z0NHNEtEeCgJjqzBjsT6Zfua/E5bD3k2TqNmPQZkYIV0hD3uqC7ORBBB43yQBRDSBSY7kruoyqIZbqrJobhnn1DsOSViTRLJlBMhssGlgbnGmWa/mwoeUd8JVahA409cFlfmFu5mHbGBlFkCOwtD09AY8nE3henGFIJ0M02v8QqXPKCgc/teWd75zKEi1q33vNKJCxreEuAGsNGSmrdVU5clgAt4UQVSnT1PoWWsfOEVElyK4mrtWLJ26l4U5UO3bIuiqvWQ4IQJrWJJjaSOLJEGY96q5huNM4ryTDqaUSIhDdeB+V9NmidL9kgCg1KkLayq6A+J4wTE8eRHKP9+5gXy9Oi5QKGumPIyYGpBTawWlA+dkbx3eXdAKOX2zcUCfSVOn/LlMsrbKaHMaWt9Iz/6npJQnhmNsLtUIDI6qp1OKjt4ov+WAaQe7/sE0hqqBPCkM77jNijEpLHOjlDti7dA0hWgCJnIhpTr6EMF8k09bW087yMtxZkohxU3mlsHpeXIwcqPf230kQcKgW8KgXsKOnX8cx73hXqo9pi2aTzRl10JZyxQ33gbCqVIQwnYfk+zlUkAoMg0Tb10FDX07/m29CO1ovX3Ows2R/IkDDEBDOKja+YIFjMz83jiVMdyM8IKcHrYKDn3eWElSGTQjSj9LwNGmIRqF+Mo/XyRZvWElKAsjNFoEd74tvqgaye1HG9l+KXnHE8VIZjl7tgklfNSAP5Jq2v+HamJwb343PIypl7ShzRIWDGUiBMLfUhXPgoaE+JCayZTv2UM8TpqOwGgYg0qBVxec+ENxntafHNM8E7lZouzkyUIiDlnRKDBocCE0aOP1FEc0SEB0jOAtElvMU8B7l97jMCVecHfjAwcdXq1bSCjnFYeKIxSo7pqWnaVdjFD8a74Cb+fh8gRxkv2FdnncwxN02sNwUSQPL7+ZVQqjGrS14DMF0wnHVi3ygdizgwPvXWKmK/HBgC9MrZHVh9uFLjVer18T4jL8YAxe3G0CQOZ9u2cMq/Wv7gRj20ga7JuHwxWhQEdYyjcxJj1+FzxDtVOigoIq6jd/qaLRPXo2qRSJrs2yeWqrDSHaTabmRURLW8g7KShOa6OY4dId7AUwGstQhjiELFQND74juBK8HDcdg+hvL3eNcn21ieDs3BGK7FsEiIV3dxcRr89DQs9oOhMrYO+Py4RBCR6iEhu8tAn9Lm+5IIPC/y5fwRmj3ORgIDiMU3/3QF6ek9c9PHcXhZLU/NNdzXit7C5eICorlEI8lfE5qxXi32ETCX7TiDNBFSLuOsvmV8ihjOdi/rsxfCTNTEOC33E3lbVgR6rAEvK7HBHEgD4474tCcnJz0BRM8h23aC4Jj4QVLeE1cXookagwNJA+WgA4n4lSFw+2yep336HZCz2kJVlanPwNnMgc88V5YV+yZxNODKD+dmZmFkbJSCK5E4OwH3Z+bnSHLghRG+3dlFD6snMKx9Pqye3w8LbmM+B1BnHYsN56wPMk9VhrEtWFsgWE4OyxBS8ZIrbipyRGC4JJIbNWdEwpBT+yRhZPFlBZ5PfVbJEEfsEgfVflpEsb0GQx4JuZNEa0g/gLRWpMdKmHbA/MZ31YNlJauqf1quct6ogma1RFQgJ6NTwqfjDpCYbGYap9/sYu+o5HQJJtH34dkw52/Zr16pR0336wjCgzcQyuRFbNvCM/I1tT+hvDCkqkvEgSol7j3HBcJuIBC0QzCPC4KV9oSgDY83vZ+Hof5MkCSzYeCjEXLNyUx/eNek0hKJfBtmLnn5hBSD3luCwCK6tBBxGzlaAObuQZ8xMZ8RiWuSrQqegmQjvakjwSfisqjtzbj1blws9OX4uA+ZOxUiA29IfSUScu/KQFQS8gJ42lnICRZA3L9MWSx9EyCc7LCsFHGrpE7jr0okkEoRe3E6be6jU0ymqoVIFDiVbGSDtBCpI0qSLZFJmkOAuBFO4ErueKfSlBG3lkVgircKhIDJ49CjRZuogI9FwHtdLDgSltTJqxGMlSqoXHVlsqdnC19GjMl37fNlsFjCiv6M7R7G5eLc5N2wV0SGtstnL6iJOejKvBzKae2hWgbhqIjPAlzkNa6/FspOvnrmyuQv0LeKGbVqkgeL6DL2KJ195o1LrlPpi0tOj9gz0a2jFIkvO9AkerErsnszb8fHtuLcxu3JydYgjzGk1XEbxaDt6ep6lIwS3MjZH/loDLRtvN31KfOTRTIocQ1QwbzuKxf3HZEZflPCOn6Gq+ZomGN/FnpspGM/8Ht0fIwWC3HfetfLPovhkbHg1prFBPCwqJSsIs73k+iXyZO2E3EkXEliTgYH4KKrzlnuDgMQvGDPrcShHFdVKF/Qsn0flruEu6gAUeKMk2gsBJ9q5GHYhUIXJQyWYI3aHuUAmYhw3jafWyG5twqijuX0XWg+d4W6pc2V0i8RVCrUrzUPZp/PXIyd8EapUTjVFKpBSeu0X86O0snRC9pOus/9kcDOWuQkfYmhzDkO06eqpQc1lLLeol42Xkm3pIRM58+QAPCUXMkp7tAe9B7h0DAlbxgipO71OXqCNkxhBfPzcrwBZoLwMrDow3aQYY4z0gN0Unw+CTopTpeMkrtUgxEV5h4OB4EHX1moCTXsWijKtQqvSPCgjEAIX/M2eCeLYUL9HsB6U2zHfWxbH1VgeT1YKRVfgETsLVDIXLf2V0Yg2e3GPSVoHUX8RBVF485UdYIkBSzZOxfrAVVhfJpXiCW5I8n97cx35AUC2+Tl4uoVq4Q4nCTWoGeVFX8ALaDU397nucwo7ZDzsn7PTu9F8WLhBx1yB6cOktsX3bv4zIvI7aJIqevJeGxBXWQwyUWtj1LF0rOde28QX71YXERErAYyQUK0pRYItc7cKG7BNK1EPg4c5NHIrngPksErz9P0SDhG7SV2KSGwN5NUrq0orttjxdSO8VG6QLPv4PI/AQo7JS/WuA9QqF5gRpX/jgGdqtIp7dIQ67jOYd3QLv4DyYlRdCkRh86rOpAtcThqkNT52smaTEox5KIuKiocSqmqL6hSZxzDwQCkIXizlOPUrJwMEFf/+57b192puv8cN0mtWbsG9BiLqtsTR0Pw7KHPF613rLRHKU+wgtk0Z9gpPLHI19JRn/DbJxGeMUQWLRDdl8KNnCBxG78cJEni+oIfAI/E3NKNjJ0uRXrtV20+fSM8U6fyHjso+y9uXHvTJwLy2Y0BY/fQqlo2JUZOXJkUMWUI/obo1A2c5jDnQpWU1wPaNJhK7YKccI0kslJWCFEJjH5Xol6BSI+aFyjpt46v9qJe9Q0uVdmcOtsW5HNAi7lRTVO25wmF+56Jk2wOYGaxYsUKWhdBmwPrQ/tjOLh+x9AWodBeTKsTvnsYQrBwX5zJmI6xNw121RUUoBDlSiSUYj5BJCf/7XycrkZBAONzN48adeZu0lJIPPDAA5TlED9yq+jNUpdLFYpObd2NaQZyWyBjDrHfdVQvbP1Yz1Xf/z5s374dNm7aBAmeUCC9ByjWN2IbzjXaMyMgtyVu/sEJTomk+SlGqF5z7bVw2qmnBmN0PBIJ94E7kWv8jLXOlvGyzqcR0t7MirPMjNtOJxxDDD/hpcFi8oSYNAcZpYWV4xl0jSQRPKS5sMxQGkf1rK7EQVyb+ZP5vPvu+2H/oRk4aefRqC9Qnl4APrtwQYz3+X1z7MXClVOy7MPDoY6IRdeJE8BhBz5OgJ0carN03Gcl0+za2CvvfIGuDnJdqsk+W+9aggq/r7jySjj99NMjgWT1l3fMe84lFRANzHt+8UtiGFu3bjGcMa/O2ap96qNyaV+2I7/37d8HRx11ZJzsuDJs6r37nnvo+yg5TMZFjGgZkfxA5nDLLbfA5KFD8JCHPASOnjgKdBMUHRgTfvXCfN+/bz/NWTzqLEPqbEAgvBvs3pZCfGQd8c6oVNqGUTnTRjb9QPyOkkHtH8duA4jPnDRnywvstBuKYk7j8Zz8riTnGY/tzttvh7/9h0/CCccdB+f96pPgrLMfBv2FedKicNttT9ZFuuxiq2kFdeX4KMXFV3L6jno7R0aGcl7olM9AQgYHkHs7EkLYjrs2GMtU8JcfTGhtPNNSrMtrs4wFr/vvv5/E6Lp162BifDwWRMSanpmBdUccAaOj7M27+567aWV15coVQQSvlKKeOPPk1CTVgRNz6OAhmshVK1fBwYCYuCqL9WHSsc2bN0VNCgG+d88eOCK0EZFF/CH7H9hHZ+ZhjqjxwPWxj7vuvZfqWrNmLawO9/GlfaFeNCBXU7nxBijwna1btsBP9u+nBTA0OkFQAvuEGRGHhoZJzamqSkL7XZT+qHbg8dUY+r1+3RHk2cGn+N6ucB/HhG1jH1atWknvLoR+4zvjgSGtW7ceNKLabhpzBsGTCxhaCQWccQ8r/YrnwIGW8cmJoIwN0n1GI6Z6cu3KgUCoWmlQ5Yb1E7B+jYef/ewGuP22m2EzJs7+9fPg9NNOhNmZQwQLVLU6WzZveTunex+i1cUOnTWnqVg4k8iZZ54BxxxzjBl4/m0/YH5X5dmFSvmRw8DAy5Vcyirs8bnP6sR2br7lZtgSuP7q1asz4F/1gx/CvYErz8/PwfU33AgbNmwgJLvpxz+CO++8kwy1G8L99evXwcGDB2H37j2BUOZIP8VDXbQdfPbDH1xFh7zgmK6//nqYmZmGzZs2w5VXXgH3hDYQuPfeezchOraD35d+77vkLbznnnvpPO7NmzdTHy+55BLKHYXJnG+++edw5JFHwv6A4JiuE8shga5Zuxp++pOfws9//jNawMJym4J6NjY6Kgty/NmyZSsROUoRlHxr1qwhQsBE2D/84Q9hfoEJAPdbn3DC8cFjM8LEEsYxF+By+RWXU/179u6BO+64A7aHOe92O3DxJd8leKAq/tOf/Tz09xAcc/Qx1MfvXnoZjIyOUPnJySk4KvSfgh2x3g4HP+pHgyH5t953MTdWZZ5VcmYgLzpqOQlf0d8Vr9Z3ZPwcImO3AbkUNmPghJ+hQEybVo/C1o0rAkEcDAxxF1x79fXwgx9cHyT8DOw8+eTABCaQKQyBuu1iAJhnly9TczpQJCJkKxLn6GwLuAZnl3/jglSL8pQsLiiXkl2zpfTLl0/4HnLWdQH5cYvlxd+9BHbt3kXE8Itf/AKOP/54OPHEEwMH3kWcHJnBfffdR2ekHH300XJ0gQeAguCdURsE0bZt3w7btm0njn3VVVfRgTB4DgZKh0ed8yiKHP3mN7/JG3YCwp0U2kVbBJMGXPydi+G+gMzbj91ORIIX9mXfvn1w5113wpOf+KTAqcfh6oDsd955R+B2p+fwFclOSBCkB+6txrHfedddsC3Ug6rnoUMH4ZLvfo9OjmIJwxf+PvWUU2BLIFxMpPbVr36NiGbfvmki8Kc++UlB6o7BVT+8mhbWECl//JOfwENPPglOOilw3bl5+MY3vwXHHXdsgOt6oyoVqlZUkdrULC6vjBWEOJJ6Zcpq/RmOpb/j1HsOoe+Aj0klKKN7kA7jgUGdGPp/1NFHEWO78ce3wO2/uBu++Y17w1x8D574xMejkV7RqqKKIRWHwdfFYthBPLzGZaqTA4vH9nKDf5hfubHvXK5SWfXNEotzEF2NURp5SK5iI2xMZ2nSf3DVD4J6sBg4/kxQFVZT4Yc/7GFBzP4c7rrrF0GtWAcPfehDuW5I+m3U1dE7UllpmSZLJwzVEiSUiYDIWKQn3hFUyfBCSa0qxvDIcJBcs3D55ZdT3ZhdA8tXrjJMyFEZvPP9YNxjpYthLCh9NKDQciBFyEqZWygzNzsH27dtI84+PDIq0t0cSiRzjFIAkR4BjO+iNEGpgfbcClLpPKl9PUrsDDSujRvWE9dfEcqsDNIWJTGdGhsJASBXtyyS50RCbxA98Pg1b1VJWFp3YpZm8i2eQOoD/qrE2YL4Mz4xCiuCmnjw4CyMTnTgmOPGYd2mLbDz7nsDDG6Fu+65H772ta8GO6TCCe0QZ3NR0YO4ETLqq84Z9IVsQuwfbvDTeGWGdbwJhhpa3jUmSnxe+H69fd1cqBbddNOP4AmPfzwh1mUBITVp9ViQKI9//ONgJqg411x7XSCWnxGR2JglKxH1d4/0+W6GyGrMIuJq8jFaoZWwBWfgg+X2B8lwyy23Bk71BFL3vv3t72QIpRdmIccL+0kEVgC6+bdBJrlB7UPOeRNiBqfAL38ZyszB05/6VILr57/4xbhzmUIyIIcHjhU9P3OzMzRezCmF546PBaLvVFViHhGZm5KiQRz2b0NAkBEZ5HjmzJdb4l6EO9vWqDquX7cCVq0Yhvvun4L9B33w6q2EY449FjZs3kKS/Kc/uxm9WD0ihiHaSVZJeHYVkxzQ/lw3CNVjq5DFoLhBpX3u/srq0HsuFXDtEiqv0ovnxMdq8RAbRkjm6Kim4ANUpxDB7r/v/qBDH0WBatdccw2dbIRGKXJmBBwdExZeQHUE60oH3PiYbBn1fER8JL4xTBsj7eN9HP7td9wJq9esppVZtDd++tOfkq3DiJo5hIPL8W76NRskG88/G9aHgm2yJ7SPqiAa79cFe2drqGtXsAe2bNlM6p87DJZ0TCh32223UX/vD6pfXFjyiRlxXqgFKnfw4IHIq7C9G2+6Ca6+7npYs3pVeP++oGpNUB9PPnEH/PTnNwfm6oNn7AGSImhDNZC8AiMtLGEYyQIAUNyDTEok3cJFZCkRZBlskUFh3d1uBWNhbsYDka9cMRYk30IglINhfAeDRj8UbMd1Qdo+FDrHbD3q7QrkPgVr9WAOPwF5ZoNRN7/Qg9NOP5V02NiIDD5+oCAKl81/QTA+G7CDUgr5Jcbpci6cbmcie3RkJHG6QPSYfRy9LwcPHCBGgMSBiI6eGPQ0ISfHZ5uDLbAj2COIOCvDM1QX8Dcau7qHApkIvncIPVYBqY7cupXqWrFiAu4MRHH0UUcREaC+/tDgasVz7rBt7NOB0MYRa9eSLYTfR4R60ZuGdeE5eYjwKNEQ0VYFZESjHhFxQ1DPkIjxvEI8gQqdBmgko8qUwcGoGGgHYBAqXvg+to/jRCMaVaENpBo54UOe+jM6OkL2DpZfvXolEQRKr6OO3Bq8dQdhMeADGt5ovxx15JbwzhGhjWE4EJ4hAT/qnLPJ81MVBnHlkiHuMiPcpY1Zht27FlwBI8HsWF0xdPt4ILGEqmamD8J9e+4OMJ6ls05QxV85MRzGvhpWhDGNBALatDYww+3H7vBOMlqQnuZYudLB4YNXvvJl8OhHn5NTbtZtgMaKXtulg/C+2fUlbzgAl0fkuobo5MptHixVNSI71LWKqK4pF/WxX1KCM5j4dPJrxvSdmUozMd+5+OJglO8MiH6UNhDVvriU5tTAdzBYzioyKMetDNdNRqvP+iJ/lLDxSWRbyeXayuv0xvcYPt8PHsDjt2+j29def0NwLOwIHq6jOckDZXRHouDsIKqOt6lKVpUyzRe6cTmx+Tu5OmYBYP5e9nJwcP8DsOueO6PdvTC/GJjPDExNzZArf3x8ONiqs9AdRaONjL0q139lkLT3uHLmiAP7DUvctd3JAZFEZZIWmW0RC2tffAY/Vzy2HeDAtiTRvO1lRiA+Ir7NMpKV9T5ui/axz2BUQZ8RyqrA2cfHRsH5fJOZt+WkMqf2k1qTDeS1l+4qT7DQbPnOLsF73Tpq3+X9EBBDRHhA3hBoBK3dnhBhWsOWTRvhZzffQiro0WGB85ggJRlu4nUShtoRCWFVqTgmV8DXNgwA+VZZn3VBK4j04BwkIz1V7Ipvfa9EFfxd14uwMDcTJMlc0JSA0lDjycFbto4HJ8MQ6DpLd2h4uNHJ7O+IRIMJoPUyvUo/ffwwHitQbP3eaJlltGiqyRUdSmcKMuAyzc4gYEYcYBBVrshMveBiEgFFIRuVzM8fEdaLXHNAubyNwZrS1xKq+o7lAini0bjGwUgEX3B+aJFQHizlOCOlbJmsr8BEtS1IRPzYXTBqmLpMVXJx/SGNoWxF59DMhxCvPXxUJZj3GSoBuHbDviFFXJPNgBn97PQs1AuzQc0dQyJg6YcGPB2mw8GKGEnR5bh+BYaPm01wdxdyhFF0DVKSL2+G1Wy8lXgy0vWR0TmfOFkqajl+vjcikx4iHGwgoKVrdeOR9Kt9sxJvatcvkpJap2tnBm6J4fmWMm0VuJYafPFSlCi2SkvxrvGqpaXUlLkhnDYSnTcc3ThVbE/USNYI10R/yfVdZdICon3oGmPzmlJZKjfzrgQiH15zssRSJyYliB/VaOtdLTSfOIeWUEw9I2PDsGrNCvqz15sDtyin2xLhV7xOhKEmTwgOGtysj3uDaQ8xZpOjdJJdTocSPBTrhguENcI6tpv9gAIZlKx85Az55bLXjZzIcU+5NTTVg7IfNqgxE4xe+54IvsobLbuVIXFJCzoJbf2RR8UbIPjeYoMUeraWjWqEWUAragSbMCH/5lE6/QgMyxqKJiFyeccSjBiOEFfqrvVGWRi1STbTUJ2qj1LCe6Nm1bzVwJw1702buobjjDTVZ0kVK6BbMJTZqfngIZwkryYulo8Hzxw6GjpDGMXLW85rPDPkjuCeow7G3KogFJ1yFT1kYcG2Y37lDsuC+UHjJZ86mNyyKktcK7LnSKBfOWAa+C838764RiejtHJJnrlyMPIVNTGnPTA1u0h3WXsO8ijXSNgFUxvkfLBcEayB7oyhG5HeoLxzDc4aCYTueci74XJ8KtSzjGC9N1Iasn6AIDp4g/+xhfh6LJ7KJKappKbbbPU4ZsRPKzXa+JlWHAUF2B9Owuj53auuugLe8+f/J3VKGICG5w8T0QR7ZNfkIiXKo83xrtIMR7ACpmHVSIeijNeMuKILAI1ZhoRgvvjDZW8ZKollXBxECVSfvatuXh8RLan75ZRoJbafQtTlhi2wXE/eUVsmsS7DxaCAhwOr87nsvrlbEJC3MIgv5lLEFRKkil4sU0ZbK7kpuJxALOzy5sw7OUHYxUSVzFENL6DATLUSIeHNSB3UkEutnBn62G7cdCWNkmSp0550BypFkuvYcE5zlTjLIkTvnnTySfDyV788GOrzcCC42TEeDo+Dngmu/dngvcLI35ng1eru9yvNQYpVzEjR7S/AUId3cy32E9JrGHHsg8+75Bv9s5xBsVmJROpyaeNOPqgE4GzozmXASszCImGBxdJefEYLjPmmLg/qIFDicLFaW3cbZ7S/dJ98+rvFY+SKHy5x//SVCMRJ8F5zkU230sYKEsOQv20myJZmwaol9jv+jgQjdgHUUevg6dTtt563ubomcg6aWw8lQQJkhrdLNUQGqYGNHY1ITu83iUTVNttqWCfauA7OOffR9BvtDeW0lUtR6kjwXdW3kmGrRhIlqKQOzfaSquCttPCtPHvJS/c8R87pIHPxtolOn3Ej4SDCQitDYFotQEknBlm9jwiUbCLThuWoAMrWWumt/YXUj+SKzIs7yInAdjNJDEEUSC5TXZdKkhQSUhsJ0o6MDjKxnjCz0V7WD4AoReLGI5dNmsCVpQZnmvQFHMo+8M/GapjiuGECSqoJBlUkjkQgbXPgkurkTSe1C2jbhEVCUuNo/abLUi74ezmErdKkDRDXOijXkXCqlDuoAz2fUzy0/G5ARDsUb5ZWRmNEJQgTt1NkdnHmYj9raYZSaXpIydBce/1e+hXdidk0uWV6lz9xA5+kH7aMa5EO2d8GEZrfVSxspZGeKc/DKtREWw4MI/AieXxezr5miSN7WCRD0yQOuvhb177JEWzZ2Hkzp5AI1ZtXIv/IkCIFYjo549wymviiaDv8Ss4QEEmGhjowOjrEDgBKVhJW03uSasjJtuBOF0NSsFtpv6+mtsfNJXXFabvqErGtFCkYU3m54jvjIPKeM5AomXV8XzgK7zFwFGahUa+Ozrzwsm7hwerovmDravyRXhu9I3W+fzxKDf6uoipohtDG/CJHTvCwROHM82ylOePcBaGYeuMwhLAjgTvIVAiWjnKMA6gHiqEbiUUQmrglpNM5NCmMB9t3xVipSwml8LX7TEoloHsLH59A3ASggk3G4ax7Oc0lCQDujqk9J74cYK4xXxipvBBsDdfhuEP0ZgXHLeEXn6/O2eu7i706EoWCFv8f9klsqmdW5yCTIoo5+WJERhAlLKK4FYnAbVbgMpZrkA10E4xsuOnwbjkSt4wKlBisL5yMX8yRyyKVekfosMkapSWv82iWjQyRwUGstjGrpqfyj+2+gyZR8H2XE4xTnbswys34NRFaXSfp563L3BeKqFFTYlodKa+aRkwxKkZK5ZJqWNo6dpzJ1Zznvortl3NgR+IMlCKTSPdUwnkda82Z6HWo6FCi5A5y7EJNmoQduRJuMT8FYWKOLNw4RgQSns3PzhMjxHhEDDBFXMNXAoH0IWW0APFEhMUTyiNU88aeAcSRmjdUrDCCkuF6211Ik1jR2RRO8rKmVdG8tN1Z1pFjsvBvRREEZsd7SBubyhrALD4pgfA+ZfK8eJ+didERwqNd0RmnKvrmzMTosMCqTy4hlr3PDzNCTshYSgxdMGMi6ffrbCxNj5KLEopgZBbUtD4lfic6qqfduS6mKIIWh4AHw0QBmMtKe03oSL1m/AqnTMV0LkkNIQw6M7C230AOk1pSzbi+lxO/ahNVZG0xBUNUFFMfBFS493wB964Mj1AQ5lCXPW2IF7Pzc9QftEu6PS+ve+HSWCmtKndoRx2KGeTMmqOjNJgNNKB5+fZnCnQ6tKUjQW4ds0HGcl7lsipBOrKls0PUD8AHwVdGCsapcImweb4MUtV8XLEXqEcCAYiqmaJDlHh2CI0/Uj+dHWf+OHvTSp426WRDLzi3LtCc9GuV+nokdg25+SGSQ1LsdDxEAz85JYDVLMrQXlG+XMokgsdheCEQ3beuc4F4QBnr+5F5wAC7R0do71jYuDRIhrMwXxxbPwx0MVDFYs9TKD3jodRFyeA97RDEYztcVQvR+RZmZPaqg+CVNIv5zmYW+jCE2z0qPBeEdxkODXf5aDbHe37wUDY50BFgoZZULOHlWT8KU6GTuDls1g8DGIRprqHn0iN/lqtYmk2DTjTCTfG4D16+OZGA+voBrMrlZOLjKipyRqPTElc03NQVXYl6rU/i0Ndy1IM3i2gAYL1BHjKMj9xeuXAbYpe0EKFjCdV2TtvwCXG99BmJoBYGpRkf+YCnOi7ullJEx4BZMjuaKBNPKTddTTs5gTMYepGbnk4vpOMQQJhXDN0QShWUBEyXA3FBWRt3YKVMko76XPUJn7ygygBEYvSC2r+IZ3TQjkaRIMAKXU3EAZROlMPnuTYOP/Fx7KoF2sN8LE4dmvewfyHgXVjDGMGNb6hmz+LZ6Li8UXFmdzy38N75CcPKEtd2MB4aXUOHv0/BSuCFRB1amnTn2yTHgEtVKpQY6CEY6lKmDP50o11hRS9Yyk96C4GLRKKyH/BRBQCAXOQ7MIcc5wQU5YP30S7IET9hubrCD/dqEgXr1WrURGK1TXltqzYSzIGeKx7Zjrxqza74LVVVcu4ewdLzcTmMv8anVSuR4Dem4RSVWufKmbUG0jY4mxWpNpzcyqheCeARpaDJrOwckXpMaiNLC5YcffrGYEG8r+XkcARKH0oRxJQ7S2KzIpEwUaTf3Hplgviw9N75Ibh6cn08dg43yiH+EXGEckN4XggGK1adoUQbRiBUkgWPjvntVCkdvmCKtwAhVmAraF4uEkdFp/hgzMsQfYY5o4p0sHJVZswamEdAMSPzlNtVlq1k34adokTIkadRnbLPPhPHSfzTp3JGeiWibCONiLC1/u1zh4D+rZ4z6XEdub6PYRQRcQrkt0Sg6hjZbHWECHhocaM7TenD8KlyV53pJ88lqnCoqrK6qqeCMJFACQ9vEF8TvUXjvISRT4wifxQdREgcCz0mDvwm4pB+6Dt8GIIks0aCr8A4IpwQBqT5dRCTa5DUU1UxfGE7mGgiZlRxzmgwXAkFK7ro4lDYOyOmnBwV1BF2IcB0kEbr2ojCivtEyZxpo0vBYUQYw8OUURslCEuPPJQiry3pqn2ddMxFTgY3pI1NPPXx/Xg2n/bGaTois7kKIBPNYGAQ4dIgjxS852XyEnP0UY2KTgGfNmCRC10IJqp4LslluqtjAlXzmMFUlapGTo4p81E6QfyGlnGYj5FULE34HcpfS84OiN8xQblLECW1pTKzU6dZigBx+rOJH0nSeVEZmSB6QiD43ZO+eMugMgYj3iyZw9q5eN5JFSUiJ47L5la6tnKkCw89ZoMQI0aMdKgfc4sLNEY03GcP7ocuEYAMKNoGTqQHsM+7il4mmTnstp5d6BWDchah0wEueaD0kPYhSTWvxKEeqZjuBUxVUX1I0kJdnMmATb8TN2XfPgFM5LPLeEHy0FiGENUs5yB5XMpLkUo4dLQFIHfDgnrM9NhkzRzvTZ/NxjGDTNneCOkTyJoVzQ9GOlTJu6V7d7zFQAc5s8uG4JLS4CEChlUecQKIm9VXalMoPNieqel+J8KAzxn0sf9aYbL/zAi9Egh/2NZIkqPfZyJIfU1jw/JonCM8KyNB5DQFVsec0QosfStuTN0Hq+7/KUxgEr6JcRgeHaMkFCOja2FsYoJyhuG9rlu5kuwBF5A0YCq44WH6uwocHomn0+vB8Pg4uwu9YJoetiicLVN+G6ik0qNio0fCiynMWG0PTUHjTDXKPa2XQ37XioAF8FObLlebnGtIAG/fsZhiy0bm1Xw3Sg4vEac+X6OopYO1IWb1PPmsrB4U4MsWIsFrX4g8zCEwLKlcQSBNYoMoxatMGiqjj2MWjqawjgSntShQ5YiMSoiHjbM6kwwA3qiGvkEcaQ5kfcd7UanMnhAp5QQW1DRGd5DtowfuMIP3eiCWoGWtNmXsuxcVkDv185/8GN7/3r9mSex9Jm2j7eLxGLaxFfwQ3VWoAnVHmEhGR2BobBQ2rF6BOUlpcY4ntSbd3w40ChDVoyWMIKpWKj2ielUY5VVlDsKxBMGAqiEniNogYMQfaVv4nHHZOuO9yVdjtL5ITD7lTkrq1QDi8CI5dFKRA/paVKOEXLm0M+W1jEVsQ5eJs3vTDRdh7UVyUziQgwgfddn7VgKxUoDHaxeZq8hq+VunVOEDRh2tK5bRrOuIF0yQCgAgmx7D7GyfvJF43sAjEqiZMyYOjRrwYBuKz6pBgbS1aYN/33Td1TCPWWyGR2F85dqgUs1Bnz4LMQsLXgFjO0QcrjNE7laHG/BHRmFsbASO3LAGHrFzA2zbMJ4C0YD1PWsQKwAy5cgloNPmftyhhd4CsUM08186ichKC+XCA5DNtKGqVNpIJfaF/pXjd5y8WgmY6lSbhHVil/tDW4gkEaxyPjpSuJ+IWvuoRJi4aV61TpozEyq4bwAq0NX+uiQDVKI4Y49YKZEuFxFc/7Eclu64dL6gyx9AWsBlhEUnjnrINALaazuFNFQiMCPJyvjiO/9lJYhLgDME4rUfFgkj5TPDpnpowmr45R23whWXfCvc7sDoxh2w81HnEdx07HgU4eLCLMajBBULkTWoOhUmJAt6V3dkGFZNjMFxm9fA2Ts3wkO2boSJsHhy3+Q+2L13PxzYe4iPrULDphd8xn4uo2jsE6W8rHFP4iKMhs/2k0+BDVuOhkOzU8HuWISRkXmSIikzIOvl8/OLJFWQMKZn58hgw78xB+0ipWap+AyT0PTIUBUE20g8MgulE/rzNaYIjbx5PCkInBiAvfBZoDRGuEvSDXVw/DA3hxksglQL07159QSNBdPuUOJm54UReIrPwfgdJmjWkdHAOzi1SCmSXMVpPHmDTx25NYJlYWGRFyXxOR4SiT53x0cdY5/27/0lykXYsHYdzIVnna5weiEunB+MMu0tzrCKAcwIHXkDR2FhZhI8qC3SD+VRTQ4rxHOHqB+doQnojAZNoL8oKW4qWiUm9JF+VYKIo2MT4fc8jI6Mw2i1AMeesDNMMzsIaJW7LwusnhkJhYLQPAHcv/uXMHlgH9WLPKaPLthOIrJKDj2g3y5tfMK5nw0wvO7n98KiGueeF/MiYYtXgELTK3VYMONF+NBa2lA3y/tLrl9Ryfj8kQXYfeP34JsXfSMSEq6kczZLR1LR99kOHxnnzJXdKhSohtHmCEZJUK3Wrx6Hk45ZB2fv2ALb166mvE7zAZP2zczBtXf+Ai76wiWUyhIjILu9SVjZP8jcva5TPJdI2/HFA3DmmWfBup1nwdzefbRopWdSowTByUSOvRCQVrnh1PQs3L37IK2RdCh4jFfZkSBHg1G/6YiVsDoQhlsMZWfQ61HDoel56DvOZjgb6uob+wgnFQltLrj0pqfn4NDUNOzbfyCoj90w3g7MzyzA2lDfpjDuX4qDYtvRR5IqqFyZbDTgsA0ilPCZnZmH/aG+W++dJUSZDX3A7ILIVTGWp9MVpZgWvnqUEQTfn5vnI4eJsYVV3KmpSbjtmm+GezOw46TTYGEkMBaygBmWQyMBsYN0780eCPg9z/3Bfg7h2R5dWJibBvaAcdT1UEBsF5C/HzhgTYtpQzC64gjoDK+ExZkHoDc/FQhrRJCtYgTHA5LCAlknEOHq9VuhP3UvrF27HrasXIAtmzfEhTol+FpUyeSM4LGiunL1FRdTlkr8G71RWB5zZ3UDQ8KEhJhLDOGOw8M8XLrGshgW7P7pQ1+DyTkvsYZMsn2vuyg7oMsElSRY19i8LhnUE9ANjBTTuWKCvC4e6+E4QRy2Scer9WehuuUbMDurh+cAtd8Pc1OLM4qYNi4fdJg5dimnUWhgNEiNI9eugFOOXQdnbN8MG1dMUOPTYUL3Bg51xwOTQYos8slLFA6wCGNhUunYLmnNyeIW6uN4c8dZT4XzX/7igPTzlI2v201rKT05WbQTJnAktA/EcYJ0oJgbD2uC3RPoFo5YNQErxsKg6ZCSiggCJcZiv0dHamH6/f0Hpwmxh8MLmC0vZvLDFKBoG4QJmRgZgpUrxoO+uRLWbthAaV9WhUlbFQhlgsILPAFy5Qo8V6RPHHBoiPvFXirmXhT+sLhITgeP4TgYbRAmc2QUjwzm47xU5wVZ9OzQavQInc1RdWpeZ6j7xCHxM7z2SPBzk/DAfCDtBWY2iACd0L6fRl/9InCyx+A4Ya5BDIKxdoI5Mu5nCG3OTy2E/s3JugciT2ACQfK4apbmp9/DOegRk8J5xHxQlD6006VxjT0Q5mosENVoH4586EPhwIEZluiBGSChIIwxHKMjya95R1+fEa4ahtPOegxcc8UlbOB2+9FWQymLLc8thHHNdWgu8M0e6vzICEL7Tzn7RPjipT/jU71kvb6icwtN0imy8TjrCGAkRFVD3CmIqiZKc3Q44eo4MblKQlI87TF3pSKHkSS1xBwSkFkCeWRi6FjCTIOrxkfh6C1r4ZE7NsHKwNU2B8McJcRkmOzdgcPdcf8U7DkwC9MLfKAinhcxvHgQxioW16RuASOY+uRPf8Sj4PznPBv23j8J69etZN2uBuKyTE8YYjxKagqqVsNdOr4dtq5dCVvXTASiGaJ66MSfAMQ5kJgrp/ZCRRM+MRqQZtUoZYTsyuo8Xn3hbpUYkygFESnWrxyB8dAWZnI/9uijaaO+GqEdV4vuUkUQEugDEeNvJM65hSCxZhbhgf3TsOfgPKmbFJjR4bCO8QBLhBFyxNmDM7zpRtTJtDjIOjuqmRMTK+CEhzyS25JkfWxTiHPDpUBA3auQbKu0OakvuXG7HQkPAbaHlHjU/ulLgGE35s+tSMLiHK5fPQLr1ozByccfGdTMsTAvPbj5jr2wBpnUilFyYJC3DqXgwgypNb3FPvUbx439XbPpBNi0+WaY3Lc3MC9evQfgg3uw32PB8YOr1UOhj8i5SbUJdWJy8bMftg0eCMzu0ht+KSTHLvBK7Rj8i+mBmA9u6KsxyhLPIVzAYwIDw+7iXDFj4yyOyMSQp4Q6AoFkTpPwX4e8tc7sTIQIfyTs7rqgRp0YVKozjt8AN193M3zvsuvgf7zqfNh81BFw9/5J+OX9M7B3cg5mwmBngvoyH5B1eOEBGK9naCsuIQbqwuJdQCCc9shz4TGPfULgTAsB0KN0bzb8XuyzETxM6x+OFqLWrxwLxM5UjPr43EKPBvHAgUmazDWrxlm/VLPXy+JSGDUFmc3PkfgdGu5ESYbvY6gCER5yu9DQhpWhzcDF8YwM5NA7T9hOi5XKljjeiQ8J1shiklSBq87Mz8LUbC+oZ/PBjkKbo0d2zUL4xogAJED23fcJ6JjnFVUwJNapqdkwrqCKBn87SlDUb7Gvnc5oeB6QM5TtU4qlSg4vYuOyEmQGsdOc2Xtt3dbWM9RfXMU2i7rkffLGOTpugRfi8Bkidjdg/NFbjoC1q4IG0WWbaEWQ3KvXrKSy2IujVjGzwjlKq/3JzEbJi3X1AmNFNXYyjPeIYx4O1eLlUPe6JH0WcE0jvDMS1hXQANaMJHqgDaraQ91hwvxffezD4MDUHNx42328jQGHUbETgmK/nKwhBUne77BVw9wiMKqeori4DJK3hHBkOKiRukSg9jtGkujJU84uNYh3rPvIHRvhpC2r4KIvXQK33bWbjLY7du+CyREHd++ZgoPTYbIDAKbCZ9ee+2FkaheMBT2glonjsPEFkQhDsGb9Zhjb8jC4/MZfwpo1LDnmKSsKG9vdkS6vdIrLk6gYDfO+7s/oE1Ax0zgm8BoNHAddwo5sjEXaWI8csTPEp17hcQbk0FJrMNw8sO9+WmMZHRsnUFFWjAB8jPlHpB0e4TUYJ9Yurx30Zco5csBptC/tC+iKqlCbQ+r7UMewc2Abg9Z6hqgM9n0ej/RC6Ydq1+77yMYbGh0mOwzVFExYhqoK+wHQHpuXPd2e9W2xoyoxkL26K8W2sR6i6BFzghPiN+Gx8cSTKzogMpDdQS43uPXugIh4JDIynd4CAZDWI/py0ity4QB/tBeGg41R0dEGHlx0XoRxBsLoyqIvJzt3cPbWYVg7NE+qE6tjaL/NsRQI/yGjwt8UsBpUsx7BO6y5dcfh159wGtx/4HK454FpSG4pDnxBGLDalYIYa4c2bLC7oBeZhZf/xNgLQ+3CyOIct555SzlRXF3VMRAWxJuHz7rHrhqCT370P+HgwSmJh+/DvuDZObR7EqYm54n6DwYut+eB4J342Q9gLBg6hJCohkRvDSMPcttF8ih1iGhQPULdc/LgQVh7xDpCZsy1pTmPlPtRp7xMWgASgmL/vv2B664mbuxcTwz6WlyyoczcPCEFclk0yhC5iIhwgrojNEhEzMVg2NLkL/ZJWI96tAXQxlhkMEYXK46+J56jrjgrOQSBVSyQvSPJdYuI1Qu+c44qrtngQ04oatHQsHhXwmdmehL23X8vTTgS7lhYvZ1YsZrXn1QKOG1DFhP7DF/0VrHOzWd+kwqIcKJx8ITWdR0NaKUQ/MLJ530zzCiQySBCa39R+iMxDI2MiDex5t2agbOi2jYVDO7Z0HdUjdcecQQMBy8XnTmOoSikNvfJM0daBEpkcavuG5+ENUcEKVsvEOGgdGUm1yeuPzrERIOEMR3mGJ0LqA4hU3GhDxc84RT45EXXwQOH5ogwCOeAc7cxUWjiCFG5InF48ejVzHTQViVcDIyxP0X533QxVRnbzNQUjVnt1o7EZNE+pe2PeOb+xYXFNbXvy/pADdtOeQgcsW0bIfzB4OXZu3cvPGv/5fDtO2fpYB1GKtYdVfyrXjcaFh63bH9oaHCBJAqnT5mGiWAcIxCjP1z9196+z/4n5GD4zigeKyDqhK44q68biQ/tJN6z0AG23pysqHpBGp48Vt/qFFLf4ZCN5J52/L5tA4RJgxN3pCBh5Nhe3LQ98kZROA6OL3I8dmnGk4JD3YtzQd3qL5JBPDIyyrYByMYf0NCPVF6OkhW1sY51c2h/SqQD+g62Rw4Sz0hFNlw/bhVUqd0P84pIhetSeDYMIwdr+t4iEKmyi3QUHV5Yfgi9nlUV50OBlY7tc/CIk7bB9iM8rBzukwaB+7878k4lBzMFkgqaSc17UdCjSY4f9hBqdO+dd90NX7zyFlj0Q8I/0mq3l3wEte+ynWNcv/SfruV4YSoBF7YOTcFGtw/2B3V5PuAOakaTw1uCC3yVSQShB0eh02fkru7i/MLfhUWut/EKeU2Amzw4CWNBldkfbI/9u+6GzT/5Bpx0ygjcfdT6YIsE1xfqZkOs85Mr0bEHoEMGUZiQ/T+Feix0oLcoiITrGjGaJkNCRjZB8HifufTkIW/UBBWbLuqWugCn5fN7SoQuqiXRQDOUpn+jylCbNC3aptoo+jOmAlJOzQxTwsb5D0Jdr6Sn+2h8zGqIN6bonpKEK4ACCVIxSloJWdZm7GYKGmO6J3SduElRp9aojMRKMG3TmMXxPZS5cz7V5UydsSvh7ubTVsNYd4KMdAwjXwxSgTYihXaGA/LM4uFCXT4TE89hZFWZJQwdRQ7AuyaDS/rYoYNw28Ka4BzoAm9/lIVRqOn8QSdeLLJLoUd2DTFZWvcK+IeLXWjMB0JfvyJ4A8dlNgQnDh0Iru/6EGtELgXs0Tk5w92Pd++56Zvv2HzyE0IHei8L3pZtKzeuD7r6LOw9NA0zv7gDNv7s22G9Y5Zcnts2r4T7D/XYMxQaGAv6NKEDuUiHSYfVOKO9wQ8/dWge1gzzotVkMG6n5oIXKLgyhwNxrQuep/UrR2HtymGqa9e+g7Dn/vvhxO2b4J79DlZOBG9TYBzo7UDVDDk08tnpuXmSBl62WxLfxDPoOrIrLhh0w51uJA7ygsiRYFS61tVmPX+RnYhjI106DrvbYWONHArRa+IlorsWL16fvDLzwaM1FVTIqdmwzhI8fIgMIxSZgKoN75HHv8dH0VM4TGqFOjWwXVzX4b32XiRazeP0vFi2IJGtvFeBzzQEWg8AqsOJviFmuDAr7F9NC524FoPPiEOH0Qx3JDa1X4uni414ZHa9HmsQ6NjAc8IRoVXloCjaUNFPbrkTfnTLHXDe484OYxmmfvHGLabPL15yFUwuBKkVKpoj4gjeQ5y70OeFObQvwjpQn3HEBTd7jVIHt77imrPsd8HEbTSvtKLdhwk/Dxsh4IZbLQxKKNmpRMENVH36lyQ6MnrcaIVrRnUvBtWi2oaborwwJvqWrbwQ7bcaJHj3QJjzv1/Yd8ef/v8sF0YLdsbHEAAAAABJRU5ErkJggg==", "description": "Designed to display single value of the selected attribute or timeseries data. Widget styles are customizable.", "descriptor": { "type": "latest", "sizeX": 3, "sizeY": 3, "resources": [], - "templateHtml": "\n", + "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n previewWidth: '250px',\n previewHeight: '250px',\n absoluteHeader: true\n };\n};\n\nself.onDestroy = function() {\n};\n", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n previewWidth: '250px',\n previewHeight: '250px',\n embedTitlePanel: true\n };\n};\n\nself.onDestroy = function() {\n};\n", "settingsSchema": "", "dataKeySettingsSchema": "", "settingsDirective": "tb-value-card-widget-settings", "hasBasicMode": true, "basicModeDirective": "tb-value-card-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1.6\"},\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null}}" } }, { "alias": "horizontal_value_card", "name": "Horizontal value card", - "image": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzk5IiBoZWlnaHQ9IjEwOCIgdmlld0JveD0iMCAwIDM5OSAxMDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2RfMTI0Nl80NDQ0NykiPgo8cmVjdCB4PSI4IiB5PSI0IiB3aWR0aD0iMzgzIiBoZWlnaHQ9IjkyIiByeD0iNCIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTU3LjAwMDEgNTEuNjY2N1YzOC4zMzM0QzU3LjAwMDEgMzUuNTY2NyA1NC43NjY3IDMzLjMzMzQgNTIuMDAwMSAzMy4zMzM0QzQ5LjIzMzQgMzMuMzMzNCA0Ny4wMDAxIDM1LjU2NjcgNDcuMDAwMSAzOC4zMzM0VjUxLjY2NjdDNDQuOTgzNCA1My4xODM0IDQzLjY2NjcgNTUuNjE2NyA0My42NjY3IDU4LjMzMzRDNDMuNjY2NyA2Mi45MzM0IDQ3LjQwMDEgNjYuNjY2NyA1Mi4wMDAxIDY2LjY2NjdDNTYuNjAwMSA2Ni42NjY3IDYwLjMzMzQgNjIuOTMzNCA2MC4zMzM0IDU4LjMzMzRDNjAuMzMzNCA1NS42MTY3IDU5LjAxNjcgNTMuMTgzNCA1Ny4wMDAxIDUxLjY2NjdaTTUwLjMzMzQgMzguMzMzNEM1MC4zMzM0IDM3LjQxNjcgNTEuMDgzNCAzNi42NjY3IDUyLjAwMDEgMzYuNjY2N0M1Mi45MTY3IDM2LjY2NjcgNTMuNjY2NyAzNy40MTY3IDUzLjY2NjcgMzguMzMzNEg1Mi4wMDAxVjQwSDUzLjY2NjdWNDMuMzMzNEg1Mi4wMDAxVjQ1SDUzLjY2NjdWNDguMzMzNEg1MC4zMzM0VjM4LjMzMzRaIiBmaWxsPSIjNTQ2OUZGIi8+CjxwYXRoIGQ9Ik04NS44MzU5IDM1LjYyNVY0N0g4My44OTA2VjM1LjYyNUg4NS44MzU5Wk04OS40MDYyIDM1LjYyNVYzNy4xODc1SDgwLjM1MTZWMzUuNjI1SDg5LjQwNjJaTTkzLjk0NTMgNDcuMTU2MkM5My4zMjAzIDQ3LjE1NjIgOTIuNzU1MiA0Ny4wNTQ3IDkyLjI1IDQ2Ljg1MTZDOTEuNzUgNDYuNjQzMiA5MS4zMjI5IDQ2LjM1NDIgOTAuOTY4OCA0NS45ODQ0QzkwLjYxOTggNDUuNjE0NiA5MC4zNTE2IDQ1LjE3OTcgOTAuMTY0MSA0NC42Nzk3Qzg5Ljk3NjYgNDQuMTc5NyA4OS44ODI4IDQzLjY0MDYgODkuODgyOCA0My4wNjI1VjQyLjc1Qzg5Ljg4MjggNDIuMDg4NSA4OS45NzkyIDQxLjQ4OTYgOTAuMTcxOSA0MC45NTMxQzkwLjM2NDYgNDAuNDE2NyA5MC42MzI4IDM5Ljk1ODMgOTAuOTc2NiAzOS41NzgxQzkxLjMyMDMgMzkuMTkyNyA5MS43MjY2IDM4Ljg5ODQgOTIuMTk1MyAzOC42OTUzQzkyLjY2NDEgMzguNDkyMiA5My4xNzE5IDM4LjM5MDYgOTMuNzE4OCAzOC4zOTA2Qzk0LjMyMjkgMzguMzkwNiA5NC44NTE2IDM4LjQ5MjIgOTUuMzA0NyAzOC42OTUzQzk1Ljc1NzggMzguODk4NCA5Ni4xMzI4IDM5LjE4NDkgOTYuNDI5NyAzOS41NTQ3Qzk2LjczMTggMzkuOTE5MyA5Ni45NTU3IDQwLjM1NDIgOTcuMTAxNiA0MC44NTk0Qzk3LjI1MjYgNDEuMzY0NiA5Ny4zMjgxIDQxLjkyMTkgOTcuMzI4MSA0Mi41MzEyVjQzLjMzNTlIOTAuNzk2OVY0MS45ODQ0SDk1LjQ2ODhWNDEuODM1OUM5NS40NTgzIDQxLjQ5NzQgOTUuMzkwNiA0MS4xNzk3IDk1LjI2NTYgNDAuODgyOEM5NS4xNDU4IDQwLjU4NTkgOTQuOTYwOSA0MC4zNDY0IDk0LjcxMDkgNDAuMTY0MUM5NC40NjA5IDM5Ljk4MTggOTQuMTI3NiAzOS44OTA2IDkzLjcxMDkgMzkuODkwNkM5My4zOTg0IDM5Ljg5MDYgOTMuMTE5OCAzOS45NTgzIDkyLjg3NSA0MC4wOTM4QzkyLjYzNTQgNDAuMjI0IDkyLjQzNDkgNDAuNDE0MSA5Mi4yNzM0IDQwLjY2NDFDOTIuMTEyIDQwLjkxNDEgOTEuOTg3IDQxLjIxNjEgOTEuODk4NCA0MS41NzAzQzkxLjgxNTEgNDEuOTE5MyA5MS43NzM0IDQyLjMxMjUgOTEuNzczNCA0Mi43NVY0My4wNjI1QzkxLjc3MzQgNDMuNDMyMyA5MS44MjI5IDQzLjc3NiA5MS45MjE5IDQ0LjA5MzhDOTIuMDI2IDQ0LjQwNjIgOTIuMTc3MSA0NC42Nzk3IDkyLjM3NSA0NC45MTQxQzkyLjU3MjkgNDUuMTQ4NCA5Mi44MTI1IDQ1LjMzMzMgOTMuMDkzOCA0NS40Njg4QzkzLjM3NSA0NS41OTkgOTMuNjk1MyA0NS42NjQxIDk0LjA1NDcgNDUuNjY0MUM5NC41MDc4IDQ1LjY2NDEgOTQuOTExNSA0NS41NzI5IDk1LjI2NTYgNDUuMzkwNkM5NS42MTk4IDQ1LjIwODMgOTUuOTI3MSA0NC45NTA1IDk2LjE4NzUgNDQuNjE3Mkw5Ny4xNzk3IDQ1LjU3ODFDOTYuOTk3NCA0NS44NDM4IDk2Ljc2MDQgNDYuMDk5IDk2LjQ2ODggNDYuMzQzOEM5Ni4xNzcxIDQ2LjU4MzMgOTUuODIwMyA0Ni43Nzg2IDk1LjM5ODQgNDYuOTI5N0M5NC45ODE4IDQ3LjA4MDcgOTQuNDk3NCA0Ny4xNTYyIDkzLjk0NTMgNDcuMTU2MlpNMTAwLjkzIDQwLjI2NTZWNDdIOTkuMDQ2OVYzOC41NDY5SDEwMC44MkwxMDAuOTMgNDAuMjY1NlpNMTAwLjYyNSA0Mi40NjA5TDk5Ljk4NDQgNDIuNDUzMUM5OS45ODQ0IDQxLjg2OTggMTAwLjA1NyA0MS4zMzA3IDEwMC4yMDMgNDAuODM1OUMxMDAuMzQ5IDQwLjM0MTEgMTAwLjU2MiAzOS45MTE1IDEwMC44NDQgMzkuNTQ2OUMxMDEuMTI1IDM5LjE3NzEgMTAxLjQ3NCAzOC44OTMyIDEwMS44OTEgMzguNjk1M0MxMDIuMzEyIDM4LjQ5MjIgMTAyLjc5OSAzOC4zOTA2IDEwMy4zNTIgMzguMzkwNkMxMDMuNzM3IDM4LjM5MDYgMTA0LjA4OSAzOC40NDc5IDEwNC40MDYgMzguNTYyNUMxMDQuNzI5IDM4LjY3MTkgMTA1LjAwOCAzOC44NDY0IDEwNS4yNDIgMzkuMDg1OUMxMDUuNDgyIDM5LjMyNTUgMTA1LjY2NCAzOS42MzI4IDEwNS43ODkgNDAuMDA3OEMxMDUuOTE5IDQwLjM4MjggMTA1Ljk4NCA0MC44MzU5IDEwNS45ODQgNDEuMzY3MlY0N0gxMDQuMTAyVjQxLjUzMTJDMTA0LjEwMiA0MS4xMTk4IDEwNC4wMzkgNDAuNzk2OSAxMDMuOTE0IDQwLjU2MjVDMTAzLjc5NCA0MC4zMjgxIDEwMy42MiA0MC4xNjE1IDEwMy4zOTEgNDAuMDYyNUMxMDMuMTY3IDM5Ljk1ODMgMTAyLjg5OCAzOS45MDYyIDEwMi41ODYgMzkuOTA2MkMxMDIuMjMyIDM5LjkwNjIgMTAxLjkzIDM5Ljk3NCAxMDEuNjggNDAuMTA5NEMxMDEuNDM1IDQwLjI0NDggMTAxLjIzNCA0MC40Mjk3IDEwMS4wNzggNDAuNjY0MUMxMDAuOTIyIDQwLjg5ODQgMTAwLjgwNyA0MS4xNjkzIDEwMC43MzQgNDEuNDc2NkMxMDAuNjYxIDQxLjc4MzkgMTAwLjYyNSA0Mi4xMTIgMTAwLjYyNSA0Mi40NjA5Wk0xMDUuODY3IDQxLjk2MDlMMTA0Ljk4NCA0Mi4xNTYyQzEwNC45ODQgNDEuNjQ1OCAxMDUuMDU1IDQxLjE2NDEgMTA1LjE5NSA0MC43MTA5QzEwNS4zNDEgNDAuMjUyNiAxMDUuNTUyIDM5Ljg1MTYgMTA1LjgyOCAzOS41MDc4QzEwNi4xMDkgMzkuMTU4OSAxMDYuNDU2IDM4Ljg4NTQgMTA2Ljg2NyAzOC42ODc1QzEwNy4yNzkgMzguNDg5NiAxMDcuNzUgMzguMzkwNiAxMDguMjgxIDM4LjM5MDZDMTA4LjcxNCAzOC4zOTA2IDEwOS4wOTkgMzguNDUwNSAxMDkuNDM4IDM4LjU3MDNDMTA5Ljc4MSAzOC42ODQ5IDExMC4wNzMgMzguODY3MiAxMTAuMzEyIDM5LjExNzJDMTEwLjU1MiAzOS4zNjcyIDExMC43MzQgMzkuNjkyNyAxMTAuODU5IDQwLjA5MzhDMTEwLjk4NCA0MC40ODk2IDExMS4wNDcgNDAuOTY4OCAxMTEuMDQ3IDQxLjUzMTJWNDdIMTA5LjE1NlY0MS41MjM0QzEwOS4xNTYgNDEuMDk2NCAxMDkuMDk0IDQwLjc2NTYgMTA4Ljk2OSA0MC41MzEyQzEwOC44NDkgNDAuMjk2OSAxMDguNjc3IDQwLjEzNTQgMTA4LjQ1MyA0MC4wNDY5QzEwOC4yMjkgMzkuOTUzMSAxMDcuOTYxIDM5LjkwNjIgMTA3LjY0OCAzOS45MDYyQzEwNy4zNTcgMzkuOTA2MiAxMDcuMDk5IDM5Ljk2MDkgMTA2Ljg3NSA0MC4wNzAzQzEwNi42NTYgNDAuMTc0NSAxMDYuNDcxIDQwLjMyMjkgMTA2LjMyIDQwLjUxNTZDMTA2LjE2OSA0MC43MDMxIDEwNi4wNTUgNDAuOTE5MyAxMDUuOTc3IDQxLjE2NDFDMTA1LjkwNCA0MS40MDg5IDEwNS44NjcgNDEuNjc0NSAxMDUuODY3IDQxLjk2MDlaTTExNS4xMjUgNDAuMTcxOVY1MC4yNUgxMTMuMjQyVjM4LjU0NjlIMTE0Ljk3N0wxMTUuMTI1IDQwLjE3MTlaTTEyMC42MzMgNDIuNjk1M1Y0Mi44NTk0QzEyMC42MzMgNDMuNDc0IDEyMC41NiA0NC4wNDQzIDEyMC40MTQgNDQuNTcwM0MxMjAuMjczIDQ1LjA5MTEgMTIwLjA2MiA0NS41NDY5IDExOS43ODEgNDUuOTM3NUMxMTkuNTA1IDQ2LjMyMjkgMTE5LjE2NCA0Ni42MjI0IDExOC43NTggNDYuODM1OUMxMTguMzUyIDQ3LjA0OTUgMTE3Ljg4MyA0Ny4xNTYyIDExNy4zNTIgNDcuMTU2MkMxMTYuODI2IDQ3LjE1NjIgMTE2LjM2NSA0Ny4wNTk5IDExNS45NjkgNDYuODY3MkMxMTUuNTc4IDQ2LjY2OTMgMTE1LjI0NyA0Ni4zOTA2IDExNC45NzcgNDYuMDMxMkMxMTQuNzA2IDQ1LjY3MTkgMTE0LjQ4NyA0NS4yNSAxMTQuMzIgNDQuNzY1NkMxMTQuMTU5IDQ0LjI3NiAxMTQuMDQ0IDQzLjczOTYgMTEzLjk3NyA0My4xNTYyVjQyLjUyMzRDMTE0LjA0NCA0MS45MDM2IDExNC4xNTkgNDEuMzQxMSAxMTQuMzIgNDAuODM1OUMxMTQuNDg3IDQwLjMzMDcgMTE0LjcwNiAzOS44OTU4IDExNC45NzcgMzkuNTMxMkMxMTUuMjQ3IDM5LjE2NjcgMTE1LjU3OCAzOC44ODU0IDExNS45NjkgMzguNjg3NUMxMTYuMzU5IDM4LjQ4OTYgMTE2LjgxNSAzOC4zOTA2IDExNy4zMzYgMzguMzkwNkMxMTcuODY3IDM4LjM5MDYgMTE4LjMzOSAzOC40OTQ4IDExOC43NSAzOC43MDMxQzExOS4xNjEgMzguOTA2MiAxMTkuNTA4IDM5LjE5NzkgMTE5Ljc4OSAzOS41NzgxQzEyMC4wNyAzOS45NTMxIDEyMC4yODEgNDAuNDA2MiAxMjAuNDIyIDQwLjkzNzVDMTIwLjU2MiA0MS40NjM1IDEyMC42MzMgNDIuMDQ5NSAxMjAuNjMzIDQyLjY5NTNaTTExOC43NSA0Mi44NTk0VjQyLjY5NTNDMTE4Ljc1IDQyLjMwNDcgMTE4LjcxNCA0MS45NDI3IDExOC42NDEgNDEuNjA5NEMxMTguNTY4IDQxLjI3MDggMTE4LjQ1MyA0MC45NzQgMTE4LjI5NyA0MC43MTg4QzExOC4xNDEgNDAuNDYzNSAxMTcuOTQgNDAuMjY1NiAxMTcuNjk1IDQwLjEyNUMxMTcuNDU2IDM5Ljk3OTIgMTE3LjE2NyAzOS45MDYyIDExNi44MjggMzkuOTA2MkMxMTYuNDk1IDM5LjkwNjIgMTE2LjIwOCAzOS45NjM1IDExNS45NjkgNDAuMDc4MUMxMTUuNzI5IDQwLjE4NzUgMTE1LjUyOSA0MC4zNDExIDExNS4zNjcgNDAuNTM5MUMxMTUuMjA2IDQwLjczNyAxMTUuMDgxIDQwLjk2ODggMTE0Ljk5MiA0MS4yMzQ0QzExNC45MDQgNDEuNDk0OCAxMTQuODQxIDQxLjc3ODYgMTE0LjgwNSA0Mi4wODU5VjQzLjYwMTZDMTE0Ljg2NyA0My45NzY2IDExNC45NzQgNDQuMzIwMyAxMTUuMTI1IDQ0LjYzMjhDMTE1LjI3NiA0NC45NDUzIDExNS40OSA0NS4xOTUzIDExNS43NjYgNDUuMzgyOEMxMTYuMDQ3IDQ1LjU2NTEgMTE2LjQwNiA0NS42NTYyIDExNi44NDQgNDUuNjU2MkMxMTcuMTgyIDQ1LjY1NjIgMTE3LjQ3MSA0NS41ODMzIDExNy43MTEgNDUuNDM3NUMxMTcuOTUxIDQ1LjI5MTcgMTE4LjE0NiA0NS4wOTExIDExOC4yOTcgNDQuODM1OUMxMTguNDUzIDQ0LjU3NTUgMTE4LjU2OCA0NC4yNzYgMTE4LjY0MSA0My45Mzc1QzExOC43MTQgNDMuNTk5IDExOC43NSA0My4yMzk2IDExOC43NSA0Mi44NTk0Wk0xMjYuMjExIDQ3LjE1NjJDMTI1LjU4NiA0Ny4xNTYyIDEyNS4wMjEgNDcuMDU0NyAxMjQuNTE2IDQ2Ljg1MTZDMTI0LjAxNiA0Ni42NDMyIDEyMy41ODkgNDYuMzU0MiAxMjMuMjM0IDQ1Ljk4NDRDMTIyLjg4NSA0NS42MTQ2IDEyMi42MTcgNDUuMTc5NyAxMjIuNDMgNDQuNjc5N0MxMjIuMjQyIDQ0LjE3OTcgMTIyLjE0OCA0My42NDA2IDEyMi4xNDggNDMuMDYyNVY0Mi43NUMxMjIuMTQ4IDQyLjA4ODUgMTIyLjI0NSA0MS40ODk2IDEyMi40MzggNDAuOTUzMUMxMjIuNjMgNDAuNDE2NyAxMjIuODk4IDM5Ljk1ODMgMTIzLjI0MiAzOS41NzgxQzEyMy41ODYgMzkuMTkyNyAxMjMuOTkyIDM4Ljg5ODQgMTI0LjQ2MSAzOC42OTUzQzEyNC45MyAzOC40OTIyIDEyNS40MzggMzguMzkwNiAxMjUuOTg0IDM4LjM5MDZDMTI2LjU4OSAzOC4zOTA2IDEyNy4xMTcgMzguNDkyMiAxMjcuNTcgMzguNjk1M0MxMjguMDIzIDM4Ljg5ODQgMTI4LjM5OCAzOS4xODQ5IDEyOC42OTUgMzkuNTU0N0MxMjguOTk3IDM5LjkxOTMgMTI5LjIyMSA0MC4zNTQyIDEyOS4zNjcgNDAuODU5NEMxMjkuNTE4IDQxLjM2NDYgMTI5LjU5NCA0MS45MjE5IDEyOS41OTQgNDIuNTMxMlY0My4zMzU5SDEyMy4wNjJWNDEuOTg0NEgxMjcuNzM0VjQxLjgzNTlDMTI3LjcyNCA0MS40OTc0IDEyNy42NTYgNDEuMTc5NyAxMjcuNTMxIDQwLjg4MjhDMTI3LjQxMSA0MC41ODU5IDEyNy4yMjcgNDAuMzQ2NCAxMjYuOTc3IDQwLjE2NDFDMTI2LjcyNyAzOS45ODE4IDEyNi4zOTMgMzkuODkwNiAxMjUuOTc3IDM5Ljg5MDZDMTI1LjY2NCAzOS44OTA2IDEyNS4zODUgMzkuOTU4MyAxMjUuMTQxIDQwLjA5MzhDMTI0LjkwMSA0MC4yMjQgMTI0LjcwMSA0MC40MTQxIDEyNC41MzkgNDAuNjY0MUMxMjQuMzc4IDQwLjkxNDEgMTI0LjI1MyA0MS4yMTYxIDEyNC4xNjQgNDEuNTcwM0MxMjQuMDgxIDQxLjkxOTMgMTI0LjAzOSA0Mi4zMTI1IDEyNC4wMzkgNDIuNzVWNDMuMDYyNUMxMjQuMDM5IDQzLjQzMjMgMTI0LjA4OSA0My43NzYgMTI0LjE4OCA0NC4wOTM4QzEyNC4yOTIgNDQuNDA2MiAxMjQuNDQzIDQ0LjY3OTcgMTI0LjY0MSA0NC45MTQxQzEyNC44MzkgNDUuMTQ4NCAxMjUuMDc4IDQ1LjMzMzMgMTI1LjM1OSA0NS40Njg4QzEyNS42NDEgNDUuNTk5IDEyNS45NjEgNDUuNjY0MSAxMjYuMzIgNDUuNjY0MUMxMjYuNzczIDQ1LjY2NDEgMTI3LjE3NyA0NS41NzI5IDEyNy41MzEgNDUuMzkwNkMxMjcuODg1IDQ1LjIwODMgMTI4LjE5MyA0NC45NTA1IDEyOC40NTMgNDQuNjE3MkwxMjkuNDQ1IDQ1LjU3ODFDMTI5LjI2MyA0NS44NDM4IDEyOS4wMjYgNDYuMDk5IDEyOC43MzQgNDYuMzQzOEMxMjguNDQzIDQ2LjU4MzMgMTI4LjA4NiA0Ni43Nzg2IDEyNy42NjQgNDYuOTI5N0MxMjcuMjQ3IDQ3LjA4MDcgMTI2Ljc2MyA0Ny4xNTYyIDEyNi4yMTEgNDcuMTU2MlpNMTMzLjIwMyA0MC4xNTYyVjQ3SDEzMS4zMlYzOC41NDY5SDEzMy4xMTdMMTMzLjIwMyA0MC4xNTYyWk0xMzUuNzg5IDM4LjQ5MjJMMTM1Ljc3MyA0MC4yNDIyQzEzNS42NTkgNDAuMjIxNCAxMzUuNTM0IDQwLjIwNTcgMTM1LjM5OCA0MC4xOTUzQzEzNS4yNjggNDAuMTg0OSAxMzUuMTM4IDQwLjE3OTcgMTM1LjAwOCA0MC4xNzk3QzEzNC42ODUgNDAuMTc5NyAxMzQuNDAxIDQwLjIyNjYgMTM0LjE1NiA0MC4zMjAzQzEzMy45MTEgNDAuNDA4OSAxMzMuNzA2IDQwLjUzOTEgMTMzLjUzOSA0MC43MTA5QzEzMy4zNzggNDAuODc3NiAxMzMuMjUzIDQxLjA4MDcgMTMzLjE2NCA0MS4zMjAzQzEzMy4wNzYgNDEuNTU5OSAxMzMuMDIzIDQxLjgyODEgMTMzLjAwOCA0Mi4xMjVMMTMyLjU3OCA0Mi4xNTYyQzEzMi41NzggNDEuNjI1IDEzMi42MyA0MS4xMzI4IDEzMi43MzQgNDAuNjc5N0MxMzIuODM5IDQwLjIyNjYgMTMyLjk5NSAzOS44MjgxIDEzMy4yMDMgMzkuNDg0NEMxMzMuNDE3IDM5LjE0MDYgMTMzLjY4MiAzOC44NzI0IDEzNCAzOC42Nzk3QzEzNC4zMjMgMzguNDg3IDEzNC42OTUgMzguMzkwNiAxMzUuMTE3IDM4LjM5MDZDMTM1LjIzMiAzOC4zOTA2IDEzNS4zNTQgMzguNDAxIDEzNS40ODQgMzguNDIxOUMxMzUuNjIgMzguNDQyNyAxMzUuNzIxIDM4LjQ2NjEgMTM1Ljc4OSAzOC40OTIyWk0xNDEuNzAzIDQ1LjMwNDdWNDEuMjczNEMxNDEuNzAzIDQwLjk3MTQgMTQxLjY0OCA0MC43MTA5IDE0MS41MzkgNDAuNDkyMkMxNDEuNDMgNDAuMjczNCAxNDEuMjYzIDQwLjEwNDIgMTQxLjAzOSAzOS45ODQ0QzE0MC44MiAzOS44NjQ2IDE0MC41NDQgMzkuODA0NyAxNDAuMjExIDM5LjgwNDdDMTM5LjkwNCAzOS44MDQ3IDEzOS42MzggMzkuODU2OCAxMzkuNDE0IDM5Ljk2MDlDMTM5LjE5IDQwLjA2NTEgMTM5LjAxNiA0MC4yMDU3IDEzOC44OTEgNDAuMzgyOEMxMzguNzY2IDQwLjU1OTkgMTM4LjcwMyA0MC43NjA0IDEzOC43MDMgNDAuOTg0NEgxMzYuODI4QzEzNi44MjggNDAuNjUxIDEzNi45MDkgNDAuMzI4MSAxMzcuMDcgNDAuMDE1NkMxMzcuMjMyIDM5LjcwMzEgMTM3LjQ2NiAzOS40MjQ1IDEzNy43NzMgMzkuMTc5N0MxMzguMDgxIDM4LjkzNDkgMTM4LjQ0OCAzOC43NDIyIDEzOC44NzUgMzguNjAxNkMxMzkuMzAyIDM4LjQ2MDkgMTM5Ljc4MSAzOC4zOTA2IDE0MC4zMTIgMzguMzkwNkMxNDAuOTQ4IDM4LjM5MDYgMTQxLjUxIDM4LjQ5NzQgMTQyIDM4LjcxMDlDMTQyLjQ5NSAzOC45MjQ1IDE0Mi44ODMgMzkuMjQ3NCAxNDMuMTY0IDM5LjY3OTdDMTQzLjQ1MSA0MC4xMDY4IDE0My41OTQgNDAuNjQzMiAxNDMuNTk0IDQxLjI4OTFWNDUuMDQ2OUMxNDMuNTk0IDQ1LjQzMjMgMTQzLjYyIDQ1Ljc3ODYgMTQzLjY3MiA0Ni4wODU5QzE0My43MjkgNDYuMzg4IDE0My44MSA0Ni42NTEgMTQzLjkxNCA0Ni44NzVWNDdIMTQxLjk4NEMxNDEuODk2IDQ2Ljc5NjkgMTQxLjgyNiA0Ni41MzkxIDE0MS43NzMgNDYuMjI2NkMxNDEuNzI3IDQ1LjkwODkgMTQxLjcwMyA0NS42MDE2IDE0MS43MDMgNDUuMzA0N1pNMTQxLjk3NyA0MS44NTk0TDE0MS45OTIgNDMuMDIzNEgxNDAuNjQxQzE0MC4yOTIgNDMuMDIzNCAxMzkuOTg0IDQzLjA1NzMgMTM5LjcxOSA0My4xMjVDMTM5LjQ1MyA0My4xODc1IDEzOS4yMzIgNDMuMjgxMiAxMzkuMDU1IDQzLjQwNjJDMTM4Ljg3OCA0My41MzEyIDEzOC43NDUgNDMuNjgyMyAxMzguNjU2IDQzLjg1OTRDMTM4LjU2OCA0NC4wMzY1IDEzOC41MjMgNDQuMjM3IDEzOC41MjMgNDQuNDYwOUMxMzguNTIzIDQ0LjY4NDkgMTM4LjU3NiA0NC44OTA2IDEzOC42OCA0NS4wNzgxQzEzOC43ODQgNDUuMjYwNCAxMzguOTM1IDQ1LjQwMzYgMTM5LjEzMyA0NS41MDc4QzEzOS4zMzYgNDUuNjEyIDEzOS41ODEgNDUuNjY0MSAxMzkuODY3IDQ1LjY2NDFDMTQwLjI1MyA0NS42NjQxIDE0MC41ODkgNDUuNTg1OSAxNDAuODc1IDQ1LjQyOTdDMTQxLjE2NyA0NS4yNjgyIDE0MS4zOTYgNDUuMDcyOSAxNDEuNTYyIDQ0Ljg0MzhDMTQxLjcyOSA0NC42MDk0IDE0MS44MTggNDQuMzg4IDE0MS44MjggNDQuMTc5N0wxNDIuNDM4IDQ1LjAxNTZDMTQyLjM3NSA0NS4yMjkyIDE0Mi4yNjggNDUuNDU4MyAxNDIuMTE3IDQ1LjcwMzFDMTQxLjk2NiA0NS45NDc5IDE0MS43NjggNDYuMTgyMyAxNDEuNTIzIDQ2LjQwNjJDMTQxLjI4NCA0Ni42MjUgMTQwLjk5NSA0Ni44MDQ3IDE0MC42NTYgNDYuOTQ1M0MxNDAuMzIzIDQ3LjA4NTkgMTM5LjkzOCA0Ny4xNTYyIDEzOS41IDQ3LjE1NjJDMTM4Ljk0OCA0Ny4xNTYyIDEzOC40NTYgNDcuMDQ2OSAxMzguMDIzIDQ2LjgyODFDMTM3LjU5MSA0Ni42MDQyIDEzNy4yNTMgNDYuMzA0NyAxMzcuMDA4IDQ1LjkyOTdDMTM2Ljc2MyA0NS41NDk1IDEzNi42NDEgNDUuMTE5OCAxMzYuNjQxIDQ0LjY0MDZDMTM2LjY0MSA0NC4xOTI3IDEzNi43MjQgNDMuNzk2OSAxMzYuODkxIDQzLjQ1MzFDMTM3LjA2MiA0My4xMDQyIDEzNy4zMTIgNDIuODEyNSAxMzcuNjQxIDQyLjU3ODFDMTM3Ljk3NCA0Mi4zNDM4IDEzOC4zOCA0Mi4xNjY3IDEzOC44NTkgNDIuMDQ2OUMxMzkuMzM5IDQxLjkyMTkgMTM5Ljg4NSA0MS44NTk0IDE0MC41IDQxLjg1OTRIMTQxLjk3N1pNMTQ5LjY4OCAzOC41NDY5VjM5LjkyMTlIMTQ0LjkyMlYzOC41NDY5SDE0OS42ODhaTTE0Ni4yOTcgMzYuNDc2NkgxNDguMThWNDQuNjY0MUMxNDguMTggNDQuOTI0NSAxNDguMjE2IDQ1LjEyNSAxNDguMjg5IDQ1LjI2NTZDMTQ4LjM2NyA0NS40MDEgMTQ4LjQ3NCA0NS40OTIyIDE0OC42MDkgNDUuNTM5MUMxNDguNzQ1IDQ1LjU4NTkgMTQ4LjkwNCA0NS42MDk0IDE0OS4wODYgNDUuNjA5NEMxNDkuMjE2IDQ1LjYwOTQgMTQ5LjM0MSA0NS42MDE2IDE0OS40NjEgNDUuNTg1OUMxNDkuNTgxIDQ1LjU3MDMgMTQ5LjY3NyA0NS41NTQ3IDE0OS43NSA0NS41MzkxTDE0OS43NTggNDYuOTc2NkMxNDkuNjAyIDQ3LjAyMzQgMTQ5LjQxOSA0Ny4wNjUxIDE0OS4yMTEgNDcuMTAxNkMxNDkuMDA4IDQ3LjEzOCAxNDguNzczIDQ3LjE1NjIgMTQ4LjUwOCA0Ny4xNTYyQzE0OC4wNzYgNDcuMTU2MiAxNDcuNjkzIDQ3LjA4MDcgMTQ3LjM1OSA0Ni45Mjk3QzE0Ny4wMjYgNDYuNzczNCAxNDYuNzY2IDQ2LjUyMDggMTQ2LjU3OCA0Ni4xNzE5QzE0Ni4zOTEgNDUuODIyOSAxNDYuMjk3IDQ1LjM1OTQgMTQ2LjI5NyA0NC43ODEyVjM2LjQ3NjZaTTE1Ni40NzcgNDUuMDA3OFYzOC41NDY5SDE1OC4zNjdWNDdIMTU2LjU4NkwxNTYuNDc3IDQ1LjAwNzhaTTE1Ni43NDIgNDMuMjVMMTU3LjM3NSA0My4yMzQ0QzE1Ny4zNzUgNDMuODAyMSAxNTcuMzEyIDQ0LjMyNTUgMTU3LjE4OCA0NC44MDQ3QzE1Ny4wNjIgNDUuMjc4NiAxNTYuODcgNDUuNjkyNyAxNTYuNjA5IDQ2LjA0NjlDMTU2LjM0OSA0Ni4zOTU4IDE1Ni4wMTYgNDYuNjY5MyAxNTUuNjA5IDQ2Ljg2NzJDMTU1LjIwMyA0Ny4wNTk5IDE1NC43MTYgNDcuMTU2MiAxNTQuMTQ4IDQ3LjE1NjJDMTUzLjczNyA0Ny4xNTYyIDE1My4zNTkgNDcuMDk2NCAxNTMuMDE2IDQ2Ljk3NjZDMTUyLjY3MiA0Ni44NTY4IDE1Mi4zNzUgNDYuNjcxOSAxNTIuMTI1IDQ2LjQyMTlDMTUxLjg4IDQ2LjE3MTkgMTUxLjY5IDQ1Ljg0NjQgMTUxLjU1NSA0NS40NDUzQzE1MS40MTkgNDUuMDQ0MyAxNTEuMzUyIDQ0LjU2NTEgMTUxLjM1MiA0NC4wMDc4VjM4LjU0NjlIMTUzLjIzNFY0NC4wMjM0QzE1My4yMzQgNDQuMzMwNyAxNTMuMjcxIDQ0LjU4ODUgMTUzLjM0NCA0NC43OTY5QzE1My40MTcgNDUgMTUzLjUxNiA0NS4xNjQxIDE1My42NDEgNDUuMjg5MUMxNTMuNzY2IDQ1LjQxNDEgMTUzLjkxMSA0NS41MDI2IDE1NC4wNzggNDUuNTU0N0MxNTQuMjQ1IDQ1LjYwNjggMTU0LjQyMiA0NS42MzI4IDE1NC42MDkgNDUuNjMyOEMxNTUuMTQ2IDQ1LjYzMjggMTU1LjU2OCA0NS41Mjg2IDE1NS44NzUgNDUuMzIwM0MxNTYuMTg4IDQ1LjEwNjggMTU2LjQwOSA0NC44MjAzIDE1Ni41MzkgNDQuNDYwOUMxNTYuNjc0IDQ0LjEwMTYgMTU2Ljc0MiA0My42OTc5IDE1Ni43NDIgNDMuMjVaTTE2Mi40MzggNDAuMTU2MlY0N0gxNjAuNTU1VjM4LjU0NjlIMTYyLjM1MkwxNjIuNDM4IDQwLjE1NjJaTTE2NS4wMjMgMzguNDkyMkwxNjUuMDA4IDQwLjI0MjJDMTY0Ljg5MyA0MC4yMjE0IDE2NC43NjggNDAuMjA1NyAxNjQuNjMzIDQwLjE5NTNDMTY0LjUwMyA0MC4xODQ5IDE2NC4zNzIgNDAuMTc5NyAxNjQuMjQyIDQwLjE3OTdDMTYzLjkxOSA0MC4xNzk3IDE2My42MzUgNDAuMjI2NiAxNjMuMzkxIDQwLjMyMDNDMTYzLjE0NiA0MC40MDg5IDE2Mi45NCA0MC41MzkxIDE2Mi43NzMgNDAuNzEwOUMxNjIuNjEyIDQwLjg3NzYgMTYyLjQ4NyA0MS4wODA3IDE2Mi4zOTggNDEuMzIwM0MxNjIuMzEgNDEuNTU5OSAxNjIuMjU4IDQxLjgyODEgMTYyLjI0MiA0Mi4xMjVMMTYxLjgxMiA0Mi4xNTYyQzE2MS44MTIgNDEuNjI1IDE2MS44NjUgNDEuMTMyOCAxNjEuOTY5IDQwLjY3OTdDMTYyLjA3MyA0MC4yMjY2IDE2Mi4yMjkgMzkuODI4MSAxNjIuNDM4IDM5LjQ4NDRDMTYyLjY1MSAzOS4xNDA2IDE2Mi45MTcgMzguODcyNCAxNjMuMjM0IDM4LjY3OTdDMTYzLjU1NyAzOC40ODcgMTYzLjkzIDM4LjM5MDYgMTY0LjM1MiAzOC4zOTA2QzE2NC40NjYgMzguMzkwNiAxNjQuNTg5IDM4LjQwMSAxNjQuNzE5IDM4LjQyMTlDMTY0Ljg1NCAzOC40NDI3IDE2NC45NTYgMzguNDY2MSAxNjUuMDIzIDM4LjQ5MjJaTTE3MC4wMjMgNDcuMTU2MkMxNjkuMzk4IDQ3LjE1NjIgMTY4LjgzMyA0Ny4wNTQ3IDE2OC4zMjggNDYuODUxNkMxNjcuODI4IDQ2LjY0MzIgMTY3LjQwMSA0Ni4zNTQyIDE2Ny4wNDcgNDUuOTg0NEMxNjYuNjk4IDQ1LjYxNDYgMTY2LjQzIDQ1LjE3OTcgMTY2LjI0MiA0NC42Nzk3QzE2Ni4wNTUgNDQuMTc5NyAxNjUuOTYxIDQzLjY0MDYgMTY1Ljk2MSA0My4wNjI1VjQyLjc1QzE2NS45NjEgNDIuMDg4NSAxNjYuMDU3IDQxLjQ4OTYgMTY2LjI1IDQwLjk1MzFDMTY2LjQ0MyA0MC40MTY3IDE2Ni43MTEgMzkuOTU4MyAxNjcuMDU1IDM5LjU3ODFDMTY3LjM5OCAzOS4xOTI3IDE2Ny44MDUgMzguODk4NCAxNjguMjczIDM4LjY5NTNDMTY4Ljc0MiAzOC40OTIyIDE2OS4yNSAzOC4zOTA2IDE2OS43OTcgMzguMzkwNkMxNzAuNDAxIDM4LjM5MDYgMTcwLjkzIDM4LjQ5MjIgMTcxLjM4MyAzOC42OTUzQzE3MS44MzYgMzguODk4NCAxNzIuMjExIDM5LjE4NDkgMTcyLjUwOCAzOS41NTQ3QzE3Mi44MSAzOS45MTkzIDE3My4wMzQgNDAuMzU0MiAxNzMuMTggNDAuODU5NEMxNzMuMzMxIDQxLjM2NDYgMTczLjQwNiA0MS45MjE5IDE3My40MDYgNDIuNTMxMlY0My4zMzU5SDE2Ni44NzVWNDEuOTg0NEgxNzEuNTQ3VjQxLjgzNTlDMTcxLjUzNiA0MS40OTc0IDE3MS40NjkgNDEuMTc5NyAxNzEuMzQ0IDQwLjg4MjhDMTcxLjIyNCA0MC41ODU5IDE3MS4wMzkgNDAuMzQ2NCAxNzAuNzg5IDQwLjE2NDFDMTcwLjUzOSAzOS45ODE4IDE3MC4yMDYgMzkuODkwNiAxNjkuNzg5IDM5Ljg5MDZDMTY5LjQ3NyAzOS44OTA2IDE2OS4xOTggMzkuOTU4MyAxNjguOTUzIDQwLjA5MzhDMTY4LjcxNCA0MC4yMjQgMTY4LjUxMyA0MC40MTQxIDE2OC4zNTIgNDAuNjY0MUMxNjguMTkgNDAuOTE0MSAxNjguMDY1IDQxLjIxNjEgMTY3Ljk3NyA0MS41NzAzQzE2Ny44OTMgNDEuOTE5MyAxNjcuODUyIDQyLjMxMjUgMTY3Ljg1MiA0Mi43NVY0My4wNjI1QzE2Ny44NTIgNDMuNDMyMyAxNjcuOTAxIDQzLjc3NiAxNjggNDQuMDkzOEMxNjguMTA0IDQ0LjQwNjIgMTY4LjI1NSA0NC42Nzk3IDE2OC40NTMgNDQuOTE0MUMxNjguNjUxIDQ1LjE0ODQgMTY4Ljg5MSA0NS4zMzMzIDE2OS4xNzIgNDUuNDY4OEMxNjkuNDUzIDQ1LjU5OSAxNjkuNzczIDQ1LjY2NDEgMTcwLjEzMyA0NS42NjQxQzE3MC41ODYgNDUuNjY0MSAxNzAuOTkgNDUuNTcyOSAxNzEuMzQ0IDQ1LjM5MDZDMTcxLjY5OCA0NS4yMDgzIDE3Mi4wMDUgNDQuOTUwNSAxNzIuMjY2IDQ0LjYxNzJMMTczLjI1OCA0NS41NzgxQzE3My4wNzYgNDUuODQzOCAxNzIuODM5IDQ2LjA5OSAxNzIuNTQ3IDQ2LjM0MzhDMTcyLjI1NSA0Ni41ODMzIDE3MS44OTggNDYuNzc4NiAxNzEuNDc3IDQ2LjkyOTdDMTcxLjA2IDQ3LjA4MDcgMTcwLjU3NiA0Ny4xNTYyIDE3MC4wMjMgNDcuMTU2MloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuODciLz4KPHBhdGggZD0iTTg2LjIxMDkgNjQuODM0VjY2SDgxLjkyNzdWNjQuODM0SDg2LjIxMDlaTTgyLjMzNzkgNTcuNDY4OFY2Nkg4MC44NjcyVjU3LjQ2ODhIODIuMzM3OVpNOTEuMDMxMiA2NC43Mjg1VjYxLjcwNTFDOTEuMDMxMiA2MS40Nzg1IDkwLjk5MDIgNjEuMjgzMiA5MC45MDgyIDYxLjExOTFDOTAuODI2MiA2MC45NTUxIDkwLjcwMTIgNjAuODI4MSA5MC41MzMyIDYwLjczODNDOTAuMzY5MSA2MC42NDg0IDkwLjE2MjEgNjAuNjAzNSA4OS45MTIxIDYwLjYwMzVDODkuNjgxNiA2MC42MDM1IDg5LjQ4MjQgNjAuNjQyNiA4OS4zMTQ1IDYwLjcyMDdDODkuMTQ2NSA2MC43OTg4IDg5LjAxNTYgNjAuOTA0MyA4OC45MjE5IDYxLjAzNzFDODguODI4MSA2MS4xNjk5IDg4Ljc4MTIgNjEuMzIwMyA4OC43ODEyIDYxLjQ4ODNIODcuMzc1Qzg3LjM3NSA2MS4yMzgzIDg3LjQzNTUgNjAuOTk2MSA4Ny41NTY2IDYwLjc2MTdDODcuNjc3NyA2MC41MjczIDg3Ljg1MzUgNjAuMzE4NCA4OC4wODQgNjAuMTM0OEM4OC4zMTQ1IDU5Ljk1MTIgODguNTg5OCA1OS44MDY2IDg4LjkxMDIgNTkuNzAxMkM4OS4yMzA1IDU5LjU5NTcgODkuNTg5OCA1OS41NDMgODkuOTg4MyA1OS41NDNDOTAuNDY0OCA1OS41NDMgOTAuODg2NyA1OS42MjMgOTEuMjUzOSA1OS43ODMyQzkxLjYyNSA1OS45NDM0IDkxLjkxNiA2MC4xODU1IDkyLjEyNyA2MC41MDk4QzkyLjM0MTggNjAuODMwMSA5Mi40NDkyIDYxLjIzMjQgOTIuNDQ5MiA2MS43MTY4VjY0LjUzNTJDOTIuNDQ5MiA2NC44MjQyIDkyLjQ2ODggNjUuMDg0IDkyLjUwNzggNjUuMzE0NUM5Mi41NTA4IDY1LjU0MSA5Mi42MTEzIDY1LjczODMgOTIuNjg5NSA2NS45MDYyVjY2SDkxLjI0MjJDOTEuMTc1OCA2NS44NDc3IDkxLjEyMyA2NS42NTQzIDkxLjA4NCA2NS40MTk5QzkxLjA0ODggNjUuMTgxNiA5MS4wMzEyIDY0Ljk1MTIgOTEuMDMxMiA2NC43Mjg1Wk05MS4yMzYzIDYyLjE0NDVMOTEuMjQ4IDYzLjAxNzZIOTAuMjM0NEM4OS45NzI3IDYzLjAxNzYgODkuNzQyMiA2My4wNDMgODkuNTQzIDYzLjA5MzhDODkuMzQzOCA2My4xNDA2IDg5LjE3NzcgNjMuMjEwOSA4OS4wNDQ5IDYzLjMwNDdDODguOTEyMSA2My4zOTg0IDg4LjgxMjUgNjMuNTExNyA4OC43NDYxIDYzLjY0NDVDODguNjc5NyA2My43NzczIDg4LjY0NjUgNjMuOTI3NyA4OC42NDY1IDY0LjA5NTdDODguNjQ2NSA2NC4yNjM3IDg4LjY4NTUgNjQuNDE4IDg4Ljc2MzcgNjQuNTU4NkM4OC44NDE4IDY0LjY5NTMgODguOTU1MSA2NC44MDI3IDg5LjEwMzUgNjQuODgwOUM4OS4yNTU5IDY0Ljk1OSA4OS40Mzk1IDY0Ljk5OCA4OS42NTQzIDY0Ljk5OEM4OS45NDM0IDY0Ljk5OCA5MC4xOTUzIDY0LjkzOTUgOTAuNDEwMiA2NC44MjIzQzkwLjYyODkgNjQuNzAxMiA5MC44MDA4IDY0LjU1NDcgOTAuOTI1OCA2NC4zODI4QzkxLjA1MDggNjQuMjA3IDkxLjExNzIgNjQuMDQxIDkxLjEyNSA2My44ODQ4TDkxLjU4MiA2NC41MTE3QzkxLjUzNTIgNjQuNjcxOSA5MS40NTUxIDY0Ljg0MzggOTEuMzQxOCA2NS4wMjczQzkxLjIyODUgNjUuMjEwOSA5MS4wODAxIDY1LjM4NjcgOTAuODk2NSA2NS41NTQ3QzkwLjcxNjggNjUuNzE4OCA5MC41IDY1Ljg1MzUgOTAuMjQ2MSA2NS45NTlDODkuOTk2MSA2Ni4wNjQ1IDg5LjcwNyA2Ni4xMTcyIDg5LjM3ODkgNjYuMTE3MkM4OC45NjQ4IDY2LjExNzIgODguNTk1NyA2Ni4wMzUyIDg4LjI3MTUgNjUuODcxMUM4Ny45NDczIDY1LjcwMzEgODcuNjkzNCA2NS40Nzg1IDg3LjUwOTggNjUuMTk3M0M4Ny4zMjYyIDY0LjkxMjEgODcuMjM0NCA2NC41ODk4IDg3LjIzNDQgNjQuMjMwNUM4Ny4yMzQ0IDYzLjg5NDUgODcuMjk2OSA2My41OTc3IDg3LjQyMTkgNjMuMzM5OEM4Ny41NTA4IDYzLjA3ODEgODcuNzM4MyA2Mi44NTk0IDg3Ljk4NDQgNjIuNjgzNkM4OC4yMzQ0IDYyLjUwNzggODguNTM5MSA2Mi4zNzUgODguODk4NCA2Mi4yODUyQzg5LjI1NzggNjIuMTkxNCA4OS42NjggNjIuMTQ0NSA5MC4xMjg5IDYyLjE0NDVIOTEuMjM2M1pNOTcuNzMyNCA2NC4yODMyQzk3LjczMjQgNjQuMTQyNiA5Ny42OTczIDY0LjAxNTYgOTcuNjI3IDYzLjkwMjNDOTcuNTU2NiA2My43ODUyIDk3LjQyMTkgNjMuNjc5NyA5Ny4yMjI3IDYzLjU4NTlDOTcuMDI3MyA2My40OTIyIDk2LjczODMgNjMuNDA2MiA5Ni4zNTU1IDYzLjMyODFDOTYuMDE5NSA2My4yNTM5IDk1LjcxMDkgNjMuMTY2IDk1LjQyOTcgNjMuMDY0NUM5NS4xNTIzIDYyLjk1OSA5NC45MTQxIDYyLjgzMiA5NC43MTQ4IDYyLjY4MzZDOTQuNTE1NiA2Mi41MzUyIDk0LjM2MTMgNjIuMzU5NCA5NC4yNTIgNjIuMTU2MkM5NC4xNDI2IDYxLjk1MzEgOTQuMDg3OSA2MS43MTg4IDk0LjA4NzkgNjEuNDUzMUM5NC4wODc5IDYxLjE5NTMgOTQuMTQ0NSA2MC45NTEyIDk0LjI1NzggNjAuNzIwN0M5NC4zNzExIDYwLjQ5MDIgOTQuNTMzMiA2MC4yODcxIDk0Ljc0NDEgNjAuMTExM0M5NC45NTUxIDU5LjkzNTUgOTUuMjEwOSA1OS43OTY5IDk1LjUxMTcgNTkuNjk1M0M5NS44MTY0IDU5LjU5MzggOTYuMTU2MiA1OS41NDMgOTYuNTMxMiA1OS41NDNDOTcuMDYyNSA1OS41NDMgOTcuNTE3NiA1OS42MzI4IDk3Ljg5NjUgNTkuODEyNUM5OC4yNzkzIDU5Ljk4ODMgOTguNTcyMyA2MC4yMjg1IDk4Ljc3NTQgNjAuNTMzMkM5OC45Nzg1IDYwLjgzNCA5OS4wODAxIDYxLjE3MzggOTkuMDgwMSA2MS41NTI3SDk3LjY2OEM5Ny42NjggNjEuMzg0OCA5Ny42MjUgNjEuMjI4NSA5Ny41MzkxIDYxLjA4NEM5Ny40NTcgNjAuOTM1NSA5Ny4zMzIgNjAuODE2NCA5Ny4xNjQxIDYwLjcyNjZDOTYuOTk2MSA2MC42MzI4IDk2Ljc4NTIgNjAuNTg1OSA5Ni41MzEyIDYwLjU4NTlDOTYuMjg5MSA2MC41ODU5IDk2LjA4NzkgNjAuNjI1IDk1LjkyNzcgNjAuNzAzMUM5NS43NzE1IDYwLjc3NzMgOTUuNjU0MyA2MC44NzUgOTUuNTc2MiA2MC45OTYxQzk1LjUwMiA2MS4xMTcyIDk1LjQ2NDggNjEuMjUgOTUuNDY0OCA2MS4zOTQ1Qzk1LjQ2NDggNjEuNSA5NS40ODQ0IDYxLjU5NTcgOTUuNTIzNCA2MS42ODE2Qzk1LjU2NjQgNjEuNzYzNyA5NS42MzY3IDYxLjgzOTggOTUuNzM0NCA2MS45MTAyQzk1LjgzMiA2MS45NzY2IDk1Ljk2NDggNjIuMDM5MSA5Ni4xMzI4IDYyLjA5NzdDOTYuMzA0NyA2Mi4xNTYyIDk2LjUxOTUgNjIuMjEyOSA5Ni43NzczIDYyLjI2NzZDOTcuMjYxNyA2Mi4zNjkxIDk3LjY3NzcgNjIuNSA5OC4wMjU0IDYyLjY2MDJDOTguMzc3IDYyLjgxNjQgOTguNjQ2NSA2My4wMTk1IDk4LjgzNCA2My4yNjk1Qzk5LjAyMTUgNjMuNTE1NiA5OS4xMTUyIDYzLjgyODEgOTkuMTE1MiA2NC4yMDdDOTkuMTE1MiA2NC40ODgzIDk5LjA1NDcgNjQuNzQ2MSA5OC45MzM2IDY0Ljk4MDVDOTguODE2NCA2NS4yMTA5IDk4LjY0NDUgNjUuNDEyMSA5OC40MTggNjUuNTg0Qzk4LjE5MTQgNjUuNzUyIDk3LjkxOTkgNjUuODgyOCA5Ny42MDM1IDY1Ljk3NjZDOTcuMjkxIDY2LjA3MDMgOTYuOTM5NSA2Ni4xMTcyIDk2LjU0ODggNjYuMTE3MkM5NS45NzQ2IDY2LjExNzIgOTUuNDg4MyA2Ni4wMTU2IDk1LjA4OTggNjUuODEyNUM5NC42OTE0IDY1LjYwNTUgOTQuMzg4NyA2NS4zNDE4IDk0LjE4MTYgNjUuMDIxNUM5My45Nzg1IDY0LjY5NzMgOTMuODc3IDY0LjM2MTMgOTMuODc3IDY0LjAxMzdIOTUuMjQyMkM5NS4yNTc4IDY0LjI3NTQgOTUuMzMwMSA2NC40ODQ0IDk1LjQ1OSA2NC42NDA2Qzk1LjU5MTggNjQuNzkzIDk1Ljc1NTkgNjQuOTA0MyA5NS45NTEyIDY0Ljk3NDZDOTYuMTUwNCA2NS4wNDEgOTYuMzU1NSA2NS4wNzQyIDk2LjU2NjQgNjUuMDc0MkM5Ni44MjAzIDY1LjA3NDIgOTcuMDMzMiA2NS4wNDEgOTcuMjA1MSA2NC45NzQ2Qzk3LjM3NyA2NC45MDQzIDk3LjUwNzggNjQuODEwNSA5Ny41OTc3IDY0LjY5MzRDOTcuNjg3NSA2NC41NzIzIDk3LjczMjQgNjQuNDM1NSA5Ny43MzI0IDY0LjI4MzJaTTEwMy41MDggNTkuNjYwMlY2MC42OTE0SDk5LjkzMzZWNTkuNjYwMkgxMDMuNTA4Wk0xMDAuOTY1IDU4LjEwNzRIMTAyLjM3N1Y2NC4yNDhDMTAyLjM3NyA2NC40NDM0IDEwMi40MDQgNjQuNTkzOCAxMDIuNDU5IDY0LjY5OTJDMTAyLjUxOCA2NC44MDA4IDEwMi41OTggNjQuODY5MSAxMDIuNjk5IDY0LjkwNDNDMTAyLjgwMSA2NC45Mzk1IDEwMi45MiA2NC45NTcgMTAzLjA1NyA2NC45NTdDMTAzLjE1NCA2NC45NTcgMTAzLjI0OCA2NC45NTEyIDEwMy4zMzggNjQuOTM5NUMxMDMuNDI4IDY0LjkyNzcgMTAzLjUgNjQuOTE2IDEwMy41NTUgNjQuOTA0M0wxMDMuNTYxIDY1Ljk4MjRDMTAzLjQ0MyA2Ni4wMTc2IDEwMy4zMDcgNjYuMDQ4OCAxMDMuMTUgNjYuMDc2MkMxMDIuOTk4IDY2LjEwMzUgMTAyLjgyMiA2Ni4xMTcyIDEwMi42MjMgNjYuMTE3MkMxMDIuMjk5IDY2LjExNzIgMTAyLjAxMiA2Ni4wNjA1IDEwMS43NjIgNjUuOTQ3M0MxMDEuNTEyIDY1LjgzMDEgMTAxLjMxNiA2NS42NDA2IDEwMS4xNzYgNjUuMzc4OUMxMDEuMDM1IDY1LjExNzIgMTAwLjk2NSA2NC43Njk1IDEwMC45NjUgNjQuMzM1OVY1OC4xMDc0Wk0xMTEuOSA2NC41MDU5VjU5LjY2MDJIMTEzLjMxOFY2NkgxMTEuOTgyTDExMS45IDY0LjUwNTlaTTExMi4xIDYzLjE4NzVMMTEyLjU3NCA2My4xNzU4QzExMi41NzQgNjMuNjAxNiAxMTIuNTI3IDYzLjk5NDEgMTEyLjQzNCA2NC4zNTM1QzExMi4zNCA2NC43MDkgMTEyLjE5NSA2NS4wMTk1IDExMiA2NS4yODUyQzExMS44MDUgNjUuNTQ2OSAxMTEuNTU1IDY1Ljc1MiAxMTEuMjUgNjUuOTAwNEMxMTAuOTQ1IDY2LjA0NDkgMTEwLjU4IDY2LjExNzIgMTEwLjE1NCA2Ni4xMTcyQzEwOS44NDYgNjYuMTE3MiAxMDkuNTYyIDY2LjA3MjMgMTA5LjMwNSA2NS45ODI0QzEwOS4wNDcgNjUuODkyNiAxMDguODI0IDY1Ljc1MzkgMTA4LjYzNyA2NS41NjY0QzEwOC40NTMgNjUuMzc4OSAxMDguMzExIDY1LjEzNDggMTA4LjIwOSA2NC44MzRDMTA4LjEwNyA2NC41MzMyIDEwOC4wNTcgNjQuMTczOCAxMDguMDU3IDYzLjc1NTlWNTkuNjYwMkgxMDkuNDY5VjYzLjc2NzZDMTA5LjQ2OSA2My45OTggMTA5LjQ5NiA2NC4xOTE0IDEwOS41NTEgNjQuMzQ3N0MxMDkuNjA1IDY0LjUgMTA5LjY4IDY0LjYyMyAxMDkuNzczIDY0LjcxNjhDMTA5Ljg2NyA2NC44MTA1IDEwOS45NzcgNjQuODc3IDExMC4xMDIgNjQuOTE2QzExMC4yMjcgNjQuOTU1MSAxMTAuMzU5IDY0Ljk3NDYgMTEwLjUgNjQuOTc0NkMxMTAuOTAyIDY0Ljk3NDYgMTExLjIxOSA2NC44OTY1IDExMS40NDkgNjQuNzQwMkMxMTEuNjg0IDY0LjU4MDEgMTExLjg1IDY0LjM2NTIgMTExLjk0NyA2NC4wOTU3QzExMi4wNDkgNjMuODI2MiAxMTIuMSA2My41MjM0IDExMi4xIDYzLjE4NzVaTTExNi40MzQgNjAuODc4OVY2OC40Mzc1SDExNS4wMjFWNTkuNjYwMkgxMTYuMzIyTDExNi40MzQgNjAuODc4OVpNMTIwLjU2NCA2Mi43NzE1VjYyLjg5NDVDMTIwLjU2NCA2My4zNTU1IDEyMC41MSA2My43ODMyIDEyMC40IDY0LjE3NzdDMTIwLjI5NSA2NC41Njg0IDEyMC4xMzcgNjQuOTEwMiAxMTkuOTI2IDY1LjIwMzFDMTE5LjcxOSA2NS40OTIyIDExOS40NjMgNjUuNzE2OCAxMTkuMTU4IDY1Ljg3N0MxMTguODU0IDY2LjAzNzEgMTE4LjUwMiA2Ni4xMTcyIDExOC4xMDQgNjYuMTE3MkMxMTcuNzA5IDY2LjExNzIgMTE3LjM2MyA2Ni4wNDQ5IDExNy4wNjYgNjUuOTAwNEMxMTYuNzczIDY1Ljc1MiAxMTYuNTI1IDY1LjU0MyAxMTYuMzIyIDY1LjI3MzRDMTE2LjExOSA2NS4wMDM5IDExNS45NTUgNjQuNjg3NSAxMTUuODMgNjQuMzI0MkMxMTUuNzA5IDYzLjk1NyAxMTUuNjIzIDYzLjU1NDcgMTE1LjU3MiA2My4xMTcyVjYyLjY0MjZDMTE1LjYyMyA2Mi4xNzc3IDExNS43MDkgNjEuNzU1OSAxMTUuODMgNjEuMzc3QzExNS45NTUgNjAuOTk4IDExNi4xMTkgNjAuNjcxOSAxMTYuMzIyIDYwLjM5ODRDMTE2LjUyNSA2MC4xMjUgMTE2Ljc3MyA1OS45MTQxIDExNy4wNjYgNTkuNzY1NkMxMTcuMzU5IDU5LjYxNzIgMTE3LjcwMSA1OS41NDMgMTE4LjA5MiA1OS41NDNDMTE4LjQ5IDU5LjU0MyAxMTguODQ0IDU5LjYyMTEgMTE5LjE1MiA1OS43NzczQzExOS40NjEgNTkuOTI5NyAxMTkuNzIxIDYwLjE0ODQgMTE5LjkzMiA2MC40MzM2QzEyMC4xNDMgNjAuNzE0OCAxMjAuMzAxIDYxLjA1NDcgMTIwLjQwNiA2MS40NTMxQzEyMC41MTIgNjEuODQ3NyAxMjAuNTY0IDYyLjI4NzEgMTIwLjU2NCA2Mi43NzE1Wk0xMTkuMTUyIDYyLjg5NDVWNjIuNzcxNUMxMTkuMTUyIDYyLjQ3ODUgMTE5LjEyNSA2Mi4yMDcgMTE5LjA3IDYxLjk1N0MxMTkuMDE2IDYxLjcwMzEgMTE4LjkzIDYxLjQ4MDUgMTE4LjgxMiA2MS4yODkxQzExOC42OTUgNjEuMDk3NyAxMTguNTQ1IDYwLjk0OTIgMTE4LjM2MSA2MC44NDM4QzExOC4xODIgNjAuNzM0NCAxMTcuOTY1IDYwLjY3OTcgMTE3LjcxMSA2MC42Nzk3QzExNy40NjEgNjAuNjc5NyAxMTcuMjQ2IDYwLjcyMjcgMTE3LjA2NiA2MC44MDg2QzExNi44ODcgNjAuODkwNiAxMTYuNzM2IDYxLjAwNTkgMTE2LjYxNSA2MS4xNTQzQzExNi40OTQgNjEuMzAyNyAxMTYuNCA2MS40NzY2IDExNi4zMzQgNjEuNjc1OEMxMTYuMjY4IDYxLjg3MTEgMTE2LjIyMSA2Mi4wODQgMTE2LjE5MyA2Mi4zMTQ1VjYzLjQ1MTJDMTE2LjI0IDYzLjczMjQgMTE2LjMyIDYzLjk5MDIgMTE2LjQzNCA2NC4yMjQ2QzExNi41NDcgNjQuNDU5IDExNi43MDcgNjQuNjQ2NSAxMTYuOTE0IDY0Ljc4NzFDMTE3LjEyNSA2NC45MjM4IDExNy4zOTUgNjQuOTkyMiAxMTcuNzIzIDY0Ljk5MjJDMTE3Ljk3NyA2NC45OTIyIDExOC4xOTMgNjQuOTM3NSAxMTguMzczIDY0LjgyODFDMTE4LjU1MyA2NC43MTg4IDExOC42OTkgNjQuNTY4NCAxMTguODEyIDY0LjM3N0MxMTguOTMgNjQuMTgxNiAxMTkuMDE2IDYzLjk1NyAxMTkuMDcgNjMuNzAzMUMxMTkuMTI1IDYzLjQ0OTIgMTE5LjE1MiA2My4xNzk3IDExOS4xNTIgNjIuODk0NVpNMTI1Ljg4MyA2NC42ODc1VjU3SDEyNy4zMDFWNjZIMTI2LjAxOEwxMjUuODgzIDY0LjY4NzVaTTEyMS43NTggNjIuOTAwNFY2Mi43NzczQzEyMS43NTggNjIuMjk2OSAxMjEuODE0IDYxLjg1OTQgMTIxLjkyOCA2MS40NjQ4QzEyMi4wNDEgNjEuMDY2NCAxMjIuMjA1IDYwLjcyNDYgMTIyLjQyIDYwLjQzOTVDMTIyLjYzNSA2MC4xNTA0IDEyMi44OTYgNTkuOTI5NyAxMjMuMjA1IDU5Ljc3NzNDMTIzLjUxNCA1OS42MjExIDEyMy44NjEgNTkuNTQzIDEyNC4yNDggNTkuNTQzQzEyNC42MzEgNTkuNTQzIDEyNC45NjcgNTkuNjE3MiAxMjUuMjU2IDU5Ljc2NTZDMTI1LjU0NSA1OS45MTQxIDEyNS43OTEgNjAuMTI3IDEyNS45OTQgNjAuNDA0M0MxMjYuMTk3IDYwLjY3NzcgMTI2LjM1OSA2MS4wMDU5IDEyNi40OCA2MS4zODg3QzEyNi42MDIgNjEuNzY3NiAxMjYuNjg4IDYyLjE4OTUgMTI2LjczOCA2Mi42NTQzVjYzLjA0NjlDMTI2LjY4OCA2My41IDEyNi42MDIgNjMuOTE0MSAxMjYuNDggNjQuMjg5MUMxMjYuMzU5IDY0LjY2NDEgMTI2LjE5NyA2NC45ODgzIDEyNS45OTQgNjUuMjYxN0MxMjUuNzkxIDY1LjUzNTIgMTI1LjU0MyA2NS43NDYxIDEyNS4yNSA2NS44OTQ1QzEyNC45NjEgNjYuMDQzIDEyNC42MjMgNjYuMTE3MiAxMjQuMjM2IDY2LjExNzJDMTIzLjg1NCA2Ni4xMTcyIDEyMy41MDggNjYuMDM3MSAxMjMuMTk5IDY1Ljg3N0MxMjIuODk1IDY1LjcxNjggMTIyLjYzNSA2NS40OTIyIDEyMi40MiA2NS4yMDMxQzEyMi4yMDUgNjQuOTE0MSAxMjIuMDQxIDY0LjU3NDIgMTIxLjkyOCA2NC4xODM2QzEyMS44MTQgNjMuNzg5MSAxMjEuNzU4IDYzLjM2MTMgMTIxLjc1OCA2Mi45MDA0Wk0xMjMuMTcgNjIuNzc3M1Y2Mi45MDA0QzEyMy4xNyA2My4xODk1IDEyMy4xOTUgNjMuNDU5IDEyMy4yNDYgNjMuNzA5QzEyMy4zMDEgNjMuOTU5IDEyMy4zODUgNjQuMTc5NyAxMjMuNDk4IDY0LjM3MTFDMTIzLjYxMSA2NC41NTg2IDEyMy43NTggNjQuNzA3IDEyMy45MzggNjQuODE2NEMxMjQuMTIxIDY0LjkyMTkgMTI0LjM0IDY0Ljk3NDYgMTI0LjU5NCA2NC45NzQ2QzEyNC45MTQgNjQuOTc0NiAxMjUuMTc4IDY0LjkwNDMgMTI1LjM4NSA2NC43NjM3QzEyNS41OTIgNjQuNjIzIDEyNS43NTQgNjQuNDMzNiAxMjUuODcxIDY0LjE5NTNDMTI1Ljk5MiA2My45NTMxIDEyNi4wNzQgNjMuNjgzNiAxMjYuMTE3IDYzLjM4NjdWNjIuMzI2MkMxMjYuMDk0IDYyLjA5NTcgMTI2LjA0NSA2MS44ODA5IDEyNS45NzEgNjEuNjgxNkMxMjUuOSA2MS40ODI0IDEyNS44MDUgNjEuMzA4NiAxMjUuNjg0IDYxLjE2MDJDMTI1LjU2MiA2MS4wMDc4IDEyNS40MTIgNjAuODkwNiAxMjUuMjMyIDYwLjgwODZDMTI1LjA1NyA2MC43MjI3IDEyNC44NDggNjAuNjc5NyAxMjQuNjA1IDYwLjY3OTdDMTI0LjM0OCA2MC42Nzk3IDEyNC4xMjkgNjAuNzM0NCAxMjMuOTQ5IDYwLjg0MzhDMTIzLjc3IDYwLjk1MzEgMTIzLjYyMSA2MS4xMDM1IDEyMy41MDQgNjEuMjk0OUMxMjMuMzkxIDYxLjQ4NjMgMTIzLjMwNyA2MS43MDkgMTIzLjI1MiA2MS45NjI5QzEyMy4xOTcgNjIuMjE2OCAxMjMuMTcgNjIuNDg4MyAxMjMuMTcgNjIuNzc3M1pNMTMyLjYwMiA2NC43Mjg1VjYxLjcwNTFDMTMyLjYwMiA2MS40Nzg1IDEzMi41NjEgNjEuMjgzMiAxMzIuNDc5IDYxLjExOTFDMTMyLjM5NiA2MC45NTUxIDEzMi4yNzEgNjAuODI4MSAxMzIuMTA0IDYwLjczODNDMTMxLjkzOSA2MC42NDg0IDEzMS43MzIgNjAuNjAzNSAxMzEuNDgyIDYwLjYwMzVDMTMxLjI1MiA2MC42MDM1IDEzMS4wNTMgNjAuNjQyNiAxMzAuODg1IDYwLjcyMDdDMTMwLjcxNyA2MC43OTg4IDEzMC41ODYgNjAuOTA0MyAxMzAuNDkyIDYxLjAzNzFDMTMwLjM5OCA2MS4xNjk5IDEzMC4zNTIgNjEuMzIwMyAxMzAuMzUyIDYxLjQ4ODNIMTI4Ljk0NUMxMjguOTQ1IDYxLjIzODMgMTI5LjAwNiA2MC45OTYxIDEyOS4xMjcgNjAuNzYxN0MxMjkuMjQ4IDYwLjUyNzMgMTI5LjQyNCA2MC4zMTg0IDEyOS42NTQgNjAuMTM0OEMxMjkuODg1IDU5Ljk1MTIgMTMwLjE2IDU5LjgwNjYgMTMwLjQ4IDU5LjcwMTJDMTMwLjgwMSA1OS41OTU3IDEzMS4xNiA1OS41NDMgMTMxLjU1OSA1OS41NDNDMTMyLjAzNSA1OS41NDMgMTMyLjQ1NyA1OS42MjMgMTMyLjgyNCA1OS43ODMyQzEzMy4xOTUgNTkuOTQzNCAxMzMuNDg2IDYwLjE4NTUgMTMzLjY5NyA2MC41MDk4QzEzMy45MTIgNjAuODMwMSAxMzQuMDIgNjEuMjMyNCAxMzQuMDIgNjEuNzE2OFY2NC41MzUyQzEzNC4wMiA2NC44MjQyIDEzNC4wMzkgNjUuMDg0IDEzNC4wNzggNjUuMzE0NUMxMzQuMTIxIDY1LjU0MSAxMzQuMTgyIDY1LjczODMgMTM0LjI2IDY1LjkwNjJWNjZIMTMyLjgxMkMxMzIuNzQ2IDY1Ljg0NzcgMTMyLjY5MyA2NS42NTQzIDEzMi42NTQgNjUuNDE5OUMxMzIuNjE5IDY1LjE4MTYgMTMyLjYwMiA2NC45NTEyIDEzMi42MDIgNjQuNzI4NVpNMTMyLjgwNyA2Mi4xNDQ1TDEzMi44MTggNjMuMDE3NkgxMzEuODA1QzEzMS41NDMgNjMuMDE3NiAxMzEuMzEyIDYzLjA0MyAxMzEuMTEzIDYzLjA5MzhDMTMwLjkxNCA2My4xNDA2IDEzMC43NDggNjMuMjEwOSAxMzAuNjE1IDYzLjMwNDdDMTMwLjQ4MiA2My4zOTg0IDEzMC4zODMgNjMuNTExNyAxMzAuMzE2IDYzLjY0NDVDMTMwLjI1IDYzLjc3NzMgMTMwLjIxNyA2My45Mjc3IDEzMC4yMTcgNjQuMDk1N0MxMzAuMjE3IDY0LjI2MzcgMTMwLjI1NiA2NC40MTggMTMwLjMzNCA2NC41NTg2QzEzMC40MTIgNjQuNjk1MyAxMzAuNTI1IDY0LjgwMjcgMTMwLjY3NCA2NC44ODA5QzEzMC44MjYgNjQuOTU5IDEzMS4wMSA2NC45OTggMTMxLjIyNSA2NC45OThDMTMxLjUxNCA2NC45OTggMTMxLjc2NiA2NC45Mzk1IDEzMS45OCA2NC44MjIzQzEzMi4xOTkgNjQuNzAxMiAxMzIuMzcxIDY0LjU1NDcgMTMyLjQ5NiA2NC4zODI4QzEzMi42MjEgNjQuMjA3IDEzMi42ODggNjQuMDQxIDEzMi42OTUgNjMuODg0OEwxMzMuMTUyIDY0LjUxMTdDMTMzLjEwNSA2NC42NzE5IDEzMy4wMjUgNjQuODQzOCAxMzIuOTEyIDY1LjAyNzNDMTMyLjc5OSA2NS4yMTA5IDEzMi42NSA2NS4zODY3IDEzMi40NjcgNjUuNTU0N0MxMzIuMjg3IDY1LjcxODggMTMyLjA3IDY1Ljg1MzUgMTMxLjgxNiA2NS45NTlDMTMxLjU2NiA2Ni4wNjQ1IDEzMS4yNzcgNjYuMTE3MiAxMzAuOTQ5IDY2LjExNzJDMTMwLjUzNSA2Ni4xMTcyIDEzMC4xNjYgNjYuMDM1MiAxMjkuODQyIDY1Ljg3MTFDMTI5LjUxOCA2NS43MDMxIDEyOS4yNjQgNjUuNDc4NSAxMjkuMDggNjUuMTk3M0MxMjguODk2IDY0LjkxMjEgMTI4LjgwNSA2NC41ODk4IDEyOC44MDUgNjQuMjMwNUMxMjguODA1IDYzLjg5NDUgMTI4Ljg2NyA2My41OTc3IDEyOC45OTIgNjMuMzM5OEMxMjkuMTIxIDYzLjA3ODEgMTI5LjMwOSA2Mi44NTk0IDEyOS41NTUgNjIuNjgzNkMxMjkuODA1IDYyLjUwNzggMTMwLjEwOSA2Mi4zNzUgMTMwLjQ2OSA2Mi4yODUyQzEzMC44MjggNjIuMTkxNCAxMzEuMjM4IDYyLjE0NDUgMTMxLjY5OSA2Mi4xNDQ1SDEzMi44MDdaTTEzOC42NTIgNTkuNjYwMlY2MC42OTE0SDEzNS4wNzhWNTkuNjYwMkgxMzguNjUyWk0xMzYuMTA5IDU4LjEwNzRIMTM3LjUyMVY2NC4yNDhDMTM3LjUyMSA2NC40NDM0IDEzNy41NDkgNjQuNTkzOCAxMzcuNjA0IDY0LjY5OTJDMTM3LjY2MiA2NC44MDA4IDEzNy43NDIgNjQuODY5MSAxMzcuODQ0IDY0LjkwNDNDMTM3Ljk0NSA2NC45Mzk1IDEzOC4wNjQgNjQuOTU3IDEzOC4yMDEgNjQuOTU3QzEzOC4yOTkgNjQuOTU3IDEzOC4zOTMgNjQuOTUxMiAxMzguNDgyIDY0LjkzOTVDMTM4LjU3MiA2NC45Mjc3IDEzOC42NDUgNjQuOTE2IDEzOC42OTkgNjQuOTA0M0wxMzguNzA1IDY1Ljk4MjRDMTM4LjU4OCA2Ni4wMTc2IDEzOC40NTEgNjYuMDQ4OCAxMzguMjk1IDY2LjA3NjJDMTM4LjE0MyA2Ni4xMDM1IDEzNy45NjcgNjYuMTE3MiAxMzcuNzY4IDY2LjExNzJDMTM3LjQ0MyA2Ni4xMTcyIDEzNy4xNTYgNjYuMDYwNSAxMzYuOTA2IDY1Ljk0NzNDMTM2LjY1NiA2NS44MzAxIDEzNi40NjEgNjUuNjQwNiAxMzYuMzIgNjUuMzc4OUMxMzYuMTggNjUuMTE3MiAxMzYuMTA5IDY0Ljc2OTUgMTM2LjEwOSA2NC4zMzU5VjU4LjEwNzRaTTE0Mi43ODcgNjYuMTE3MkMxNDIuMzE4IDY2LjExNzIgMTQxLjg5NSA2Ni4wNDEgMTQxLjUxNiA2NS44ODg3QzE0MS4xNDEgNjUuNzMyNCAxNDAuODIgNjUuNTE1NiAxNDAuNTU1IDY1LjIzODNDMTQwLjI5MyA2NC45NjA5IDE0MC4wOTIgNjQuNjM0OCAxMzkuOTUxIDY0LjI1OThDMTM5LjgxMSA2My44ODQ4IDEzOS43NCA2My40ODA1IDEzOS43NCA2My4wNDY5VjYyLjgxMjVDMTM5Ljc0IDYyLjMxNjQgMTM5LjgxMiA2MS44NjcyIDEzOS45NTcgNjEuNDY0OEMxNDAuMTAyIDYxLjA2MjUgMTQwLjMwMyA2MC43MTg4IDE0MC41NjEgNjAuNDMzNkMxNDAuODE4IDYwLjE0NDUgMTQxLjEyMyA1OS45MjM4IDE0MS40NzUgNTkuNzcxNUMxNDEuODI2IDU5LjYxOTEgMTQyLjIwNyA1OS41NDMgMTQyLjYxNyA1OS41NDNDMTQzLjA3IDU5LjU0MyAxNDMuNDY3IDU5LjYxOTEgMTQzLjgwNyA1OS43NzE1QzE0NC4xNDYgNTkuOTIzOCAxNDQuNDI4IDYwLjEzODcgMTQ0LjY1IDYwLjQxNkMxNDQuODc3IDYwLjY4OTUgMTQ1LjA0NSA2MS4wMTU2IDE0NS4xNTQgNjEuMzk0NUMxNDUuMjY4IDYxLjc3MzQgMTQ1LjMyNCA2Mi4xOTE0IDE0NS4zMjQgNjIuNjQ4NFY2My4yNTJIMTQwLjQyNlY2Mi4yMzgzSDE0My45M1Y2Mi4xMjdDMTQzLjkyMiA2MS44NzMgMTQzLjg3MSA2MS42MzQ4IDE0My43NzcgNjEuNDEyMUMxNDMuNjg4IDYxLjE4OTUgMTQzLjU0OSA2MS4wMDk4IDE0My4zNjEgNjAuODczQzE0My4xNzQgNjAuNzM2MyAxNDIuOTI0IDYwLjY2OCAxNDIuNjExIDYwLjY2OEMxNDIuMzc3IDYwLjY2OCAxNDIuMTY4IDYwLjcxODggMTQxLjk4NCA2MC44MjAzQzE0MS44MDUgNjAuOTE4IDE0MS42NTQgNjEuMDYwNSAxNDEuNTMzIDYxLjI0OEMxNDEuNDEyIDYxLjQzNTUgMTQxLjMxOCA2MS42NjIxIDE0MS4yNTIgNjEuOTI3N0MxNDEuMTg5IDYyLjE4OTUgMTQxLjE1OCA2Mi40ODQ0IDE0MS4xNTggNjIuODEyNVY2My4wNDY5QzE0MS4xNTggNjMuMzI0MiAxNDEuMTk1IDYzLjU4MiAxNDEuMjcgNjMuODIwM0MxNDEuMzQ4IDY0LjA1NDcgMTQxLjQ2MSA2NC4yNTk4IDE0MS42MDkgNjQuNDM1NUMxNDEuNzU4IDY0LjYxMTMgMTQxLjkzOCA2NC43NSAxNDIuMTQ4IDY0Ljg1MTZDMTQyLjM1OSA2NC45NDkyIDE0Mi42IDY0Ljk5OCAxNDIuODY5IDY0Ljk5OEMxNDMuMjA5IDY0Ljk5OCAxNDMuNTEyIDY0LjkyOTcgMTQzLjc3NyA2NC43OTNDMTQ0LjA0MyA2NC42NTYyIDE0NC4yNzMgNjQuNDYyOSAxNDQuNDY5IDY0LjIxMjlMMTQ1LjIxMyA2NC45MzM2QzE0NS4wNzYgNjUuMTMyOCAxNDQuODk4IDY1LjMyNDIgMTQ0LjY4IDY1LjUwNzhDMTQ0LjQ2MSA2NS42ODc1IDE0NC4xOTMgNjUuODM0IDE0My44NzcgNjUuOTQ3M0MxNDMuNTY0IDY2LjA2MDUgMTQzLjIwMSA2Ni4xMTcyIDE0Mi43ODcgNjYuMTE3MlpNMTUzLjY4OCA1Ny40Mzk1VjY2SDE1Mi4yNzVWNTkuMTE1MkwxNTAuMTg0IDU5LjgyNDJWNTguNjU4MkwxNTMuNTE4IDU3LjQzOTVIMTUzLjY4OFpNMTYwLjg1MiA2NC42ODc1VjU3SDE2Mi4yN1Y2NkgxNjAuOTg2TDE2MC44NTIgNjQuNjg3NVpNMTU2LjcyNyA2Mi45MDA0VjYyLjc3NzNDMTU2LjcyNyA2Mi4yOTY5IDE1Ni43ODMgNjEuODU5NCAxNTYuODk2IDYxLjQ2NDhDMTU3LjAxIDYxLjA2NjQgMTU3LjE3NCA2MC43MjQ2IDE1Ny4zODkgNjAuNDM5NUMxNTcuNjA0IDYwLjE1MDQgMTU3Ljg2NSA1OS45Mjk3IDE1OC4xNzQgNTkuNzc3M0MxNTguNDgyIDU5LjYyMTEgMTU4LjgzIDU5LjU0MyAxNTkuMjE3IDU5LjU0M0MxNTkuNiA1OS41NDMgMTU5LjkzNiA1OS42MTcyIDE2MC4yMjUgNTkuNzY1NkMxNjAuNTE0IDU5LjkxNDEgMTYwLjc2IDYwLjEyNyAxNjAuOTYzIDYwLjQwNDNDMTYxLjE2NiA2MC42Nzc3IDE2MS4zMjggNjEuMDA1OSAxNjEuNDQ5IDYxLjM4ODdDMTYxLjU3IDYxLjc2NzYgMTYxLjY1NiA2Mi4xODk1IDE2MS43MDcgNjIuNjU0M1Y2My4wNDY5QzE2MS42NTYgNjMuNSAxNjEuNTcgNjMuOTE0MSAxNjEuNDQ5IDY0LjI4OTFDMTYxLjMyOCA2NC42NjQxIDE2MS4xNjYgNjQuOTg4MyAxNjAuOTYzIDY1LjI2MTdDMTYwLjc2IDY1LjUzNTIgMTYwLjUxMiA2NS43NDYxIDE2MC4yMTkgNjUuODk0NUMxNTkuOTMgNjYuMDQzIDE1OS41OTIgNjYuMTE3MiAxNTkuMjA1IDY2LjExNzJDMTU4LjgyMiA2Ni4xMTcyIDE1OC40NzcgNjYuMDM3MSAxNTguMTY4IDY1Ljg3N0MxNTcuODYzIDY1LjcxNjggMTU3LjYwNCA2NS40OTIyIDE1Ny4zODkgNjUuMjAzMUMxNTcuMTc0IDY0LjkxNDEgMTU3LjAxIDY0LjU3NDIgMTU2Ljg5NiA2NC4xODM2QzE1Ni43ODMgNjMuNzg5MSAxNTYuNzI3IDYzLjM2MTMgMTU2LjcyNyA2Mi45MDA0Wk0xNTguMTM5IDYyLjc3NzNWNjIuOTAwNEMxNTguMTM5IDYzLjE4OTUgMTU4LjE2NCA2My40NTkgMTU4LjIxNSA2My43MDlDMTU4LjI3IDYzLjk1OSAxNTguMzU0IDY0LjE3OTcgMTU4LjQ2NyA2NC4zNzExQzE1OC41OCA2NC41NTg2IDE1OC43MjcgNjQuNzA3IDE1OC45MDYgNjQuODE2NEMxNTkuMDkgNjQuOTIxOSAxNTkuMzA5IDY0Ljk3NDYgMTU5LjU2MiA2NC45NzQ2QzE1OS44ODMgNjQuOTc0NiAxNjAuMTQ2IDY0LjkwNDMgMTYwLjM1NCA2NC43NjM3QzE2MC41NjEgNjQuNjIzIDE2MC43MjMgNjQuNDMzNiAxNjAuODQgNjQuMTk1M0MxNjAuOTYxIDYzLjk1MzEgMTYxLjA0MyA2My42ODM2IDE2MS4wODYgNjMuMzg2N1Y2Mi4zMjYyQzE2MS4wNjIgNjIuMDk1NyAxNjEuMDE0IDYxLjg4MDkgMTYwLjkzOSA2MS42ODE2QzE2MC44NjkgNjEuNDgyNCAxNjAuNzczIDYxLjMwODYgMTYwLjY1MiA2MS4xNjAyQzE2MC41MzEgNjEuMDA3OCAxNjAuMzgxIDYwLjg5MDYgMTYwLjIwMSA2MC44MDg2QzE2MC4wMjUgNjAuNzIyNyAxNTkuODE2IDYwLjY3OTcgMTU5LjU3NCA2MC42Nzk3QzE1OS4zMTYgNjAuNjc5NyAxNTkuMDk4IDYwLjczNDQgMTU4LjkxOCA2MC44NDM4QzE1OC43MzggNjAuOTUzMSAxNTguNTkgNjEuMTAzNSAxNTguNDczIDYxLjI5NDlDMTU4LjM1OSA2MS40ODYzIDE1OC4yNzUgNjEuNzA5IDE1OC4yMjEgNjEuOTYyOUMxNTguMTY2IDYyLjIxNjggMTU4LjEzOSA2Mi40ODgzIDE1OC4xMzkgNjIuNzc3M1pNMTcwLjgwOSA2NC43Mjg1VjYxLjcwNTFDMTcwLjgwOSA2MS40Nzg1IDE3MC43NjggNjEuMjgzMiAxNzAuNjg2IDYxLjExOTFDMTcwLjYwNCA2MC45NTUxIDE3MC40NzkgNjAuODI4MSAxNzAuMzExIDYwLjczODNDMTcwLjE0NiA2MC42NDg0IDE2OS45MzkgNjAuNjAzNSAxNjkuNjg5IDYwLjYwMzVDMTY5LjQ1OSA2MC42MDM1IDE2OS4yNiA2MC42NDI2IDE2OS4wOTIgNjAuNzIwN0MxNjguOTI0IDYwLjc5ODggMTY4Ljc5MyA2MC45MDQzIDE2OC42OTkgNjEuMDM3MUMxNjguNjA1IDYxLjE2OTkgMTY4LjU1OSA2MS4zMjAzIDE2OC41NTkgNjEuNDg4M0gxNjcuMTUyQzE2Ny4xNTIgNjEuMjM4MyAxNjcuMjEzIDYwLjk5NjEgMTY3LjMzNCA2MC43NjE3QzE2Ny40NTUgNjAuNTI3MyAxNjcuNjMxIDYwLjMxODQgMTY3Ljg2MSA2MC4xMzQ4QzE2OC4wOTIgNTkuOTUxMiAxNjguMzY3IDU5LjgwNjYgMTY4LjY4OCA1OS43MDEyQzE2OS4wMDggNTkuNTk1NyAxNjkuMzY3IDU5LjU0MyAxNjkuNzY2IDU5LjU0M0MxNzAuMjQyIDU5LjU0MyAxNzAuNjY0IDU5LjYyMyAxNzEuMDMxIDU5Ljc4MzJDMTcxLjQwMiA1OS45NDM0IDE3MS42OTMgNjAuMTg1NSAxNzEuOTA0IDYwLjUwOThDMTcyLjExOSA2MC44MzAxIDE3Mi4yMjcgNjEuMjMyNCAxNzIuMjI3IDYxLjcxNjhWNjQuNTM1MkMxNzIuMjI3IDY0LjgyNDIgMTcyLjI0NiA2NS4wODQgMTcyLjI4NSA2NS4zMTQ1QzE3Mi4zMjggNjUuNTQxIDE3Mi4zODkgNjUuNzM4MyAxNzIuNDY3IDY1LjkwNjJWNjZIMTcxLjAyQzE3MC45NTMgNjUuODQ3NyAxNzAuOSA2NS42NTQzIDE3MC44NjEgNjUuNDE5OUMxNzAuODI2IDY1LjE4MTYgMTcwLjgwOSA2NC45NTEyIDE3MC44MDkgNjQuNzI4NVpNMTcxLjAxNCA2Mi4xNDQ1TDE3MS4wMjUgNjMuMDE3NkgxNzAuMDEyQzE2OS43NSA2My4wMTc2IDE2OS41MiA2My4wNDMgMTY5LjMyIDYzLjA5MzhDMTY5LjEyMSA2My4xNDA2IDE2OC45NTUgNjMuMjEwOSAxNjguODIyIDYzLjMwNDdDMTY4LjY4OSA2My4zOTg0IDE2OC41OSA2My41MTE3IDE2OC41MjMgNjMuNjQ0NUMxNjguNDU3IDYzLjc3NzMgMTY4LjQyNCA2My45Mjc3IDE2OC40MjQgNjQuMDk1N0MxNjguNDI0IDY0LjI2MzcgMTY4LjQ2MyA2NC40MTggMTY4LjU0MSA2NC41NTg2QzE2OC42MTkgNjQuNjk1MyAxNjguNzMyIDY0LjgwMjcgMTY4Ljg4MSA2NC44ODA5QzE2OS4wMzMgNjQuOTU5IDE2OS4yMTcgNjQuOTk4IDE2OS40MzIgNjQuOTk4QzE2OS43MjEgNjQuOTk4IDE2OS45NzMgNjQuOTM5NSAxNzAuMTg4IDY0LjgyMjNDMTcwLjQwNiA2NC43MDEyIDE3MC41NzggNjQuNTU0NyAxNzAuNzAzIDY0LjM4MjhDMTcwLjgyOCA2NC4yMDcgMTcwLjg5NSA2NC4wNDEgMTcwLjkwMiA2My44ODQ4TDE3MS4zNTkgNjQuNTExN0MxNzEuMzEyIDY0LjY3MTkgMTcxLjIzMiA2NC44NDM4IDE3MS4xMTkgNjUuMDI3M0MxNzEuMDA2IDY1LjIxMDkgMTcwLjg1NyA2NS4zODY3IDE3MC42NzQgNjUuNTU0N0MxNzAuNDk0IDY1LjcxODggMTcwLjI3NyA2NS44NTM1IDE3MC4wMjMgNjUuOTU5QzE2OS43NzMgNjYuMDY0NSAxNjkuNDg0IDY2LjExNzIgMTY5LjE1NiA2Ni4xMTcyQzE2OC43NDIgNjYuMTE3MiAxNjguMzczIDY2LjAzNTIgMTY4LjA0OSA2NS44NzExQzE2Ny43MjUgNjUuNzAzMSAxNjcuNDcxIDY1LjQ3ODUgMTY3LjI4NyA2NS4xOTczQzE2Ny4xMDQgNjQuOTEyMSAxNjcuMDEyIDY0LjU4OTggMTY3LjAxMiA2NC4yMzA1QzE2Ny4wMTIgNjMuODk0NSAxNjcuMDc0IDYzLjU5NzcgMTY3LjE5OSA2My4zMzk4QzE2Ny4zMjggNjMuMDc4MSAxNjcuNTE2IDYyLjg1OTQgMTY3Ljc2MiA2Mi42ODM2QzE2OC4wMTIgNjIuNTA3OCAxNjguMzE2IDYyLjM3NSAxNjguNjc2IDYyLjI4NTJDMTY5LjAzNSA2Mi4xOTE0IDE2OS40NDUgNjIuMTQ0NSAxNjkuOTA2IDYyLjE0NDVIMTcxLjAxNFpNMTc4LjAxNCA1OS42NjAySDE3OS4yOTdWNjUuODI0MkMxNzkuMjk3IDY2LjM5NDUgMTc5LjE3NiA2Ni44Nzg5IDE3OC45MzQgNjcuMjc3M0MxNzguNjkxIDY3LjY3NTggMTc4LjM1NCA2Ny45Nzg1IDE3Ny45MiA2OC4xODU1QzE3Ny40ODYgNjguMzk2NSAxNzYuOTg0IDY4LjUwMiAxNzYuNDE0IDY4LjUwMkMxNzYuMTcyIDY4LjUwMiAxNzUuOTAyIDY4LjQ2NjggMTc1LjYwNSA2OC4zOTY1QzE3NS4zMTIgNjguMzI2MiAxNzUuMDI3IDY4LjIxMjkgMTc0Ljc1IDY4LjA1NjZDMTc0LjQ3NyA2Ny45MDQzIDE3NC4yNDggNjcuNzAzMSAxNzQuMDY0IDY3LjQ1MzFMMTc0LjcyNyA2Ni42MjExQzE3NC45NTMgNjYuODkwNiAxNzUuMjAzIDY3LjA4NzkgMTc1LjQ3NyA2Ny4yMTI5QzE3NS43NSA2Ny4zMzc5IDE3Ni4wMzcgNjcuNDAwNCAxNzYuMzM4IDY3LjQwMDRDMTc2LjY2MiA2Ny40MDA0IDE3Ni45MzggNjcuMzM5OCAxNzcuMTY0IDY3LjIxODhDMTc3LjM5NSA2Ny4xMDE2IDE3Ny41NzIgNjYuOTI3NyAxNzcuNjk3IDY2LjY5NzNDMTc3LjgyMiA2Ni40NjY4IDE3Ny44ODUgNjYuMTg1NSAxNzcuODg1IDY1Ljg1MzVWNjEuMDk1N0wxNzguMDE0IDU5LjY2MDJaTTE3My43MDcgNjIuOTAwNFY2Mi43NzczQzE3My43MDcgNjIuMjk2OSAxNzMuNzY2IDYxLjg1OTQgMTczLjg4MyA2MS40NjQ4QzE3NCA2MS4wNjY0IDE3NC4xNjggNjAuNzI0NiAxNzQuMzg3IDYwLjQzOTVDMTc0LjYwNSA2MC4xNTA0IDE3NC44NzEgNTkuOTI5NyAxNzUuMTg0IDU5Ljc3NzNDMTc1LjQ5NiA1OS42MjExIDE3NS44NSA1OS41NDMgMTc2LjI0NCA1OS41NDNDMTc2LjY1NCA1OS41NDMgMTc3LjAwNCA1OS42MTcyIDE3Ny4yOTMgNTkuNzY1NkMxNzcuNTg2IDU5LjkxNDEgMTc3LjgzIDYwLjEyNyAxNzguMDI1IDYwLjQwNDNDMTc4LjIyMSA2MC42Nzc3IDE3OC4zNzMgNjEuMDA1OSAxNzguNDgyIDYxLjM4ODdDMTc4LjU5NiA2MS43Njc2IDE3OC42OCA2Mi4xODk1IDE3OC43MzQgNjIuNjU0M1Y2My4wNDY5QzE3OC42ODQgNjMuNSAxNzguNTk4IDYzLjkxNDEgMTc4LjQ3NyA2NC4yODkxQzE3OC4zNTUgNjQuNjY0MSAxNzguMTk1IDY0Ljk4ODMgMTc3Ljk5NiA2NS4yNjE3QzE3Ny43OTcgNjUuNTM1MiAxNzcuNTUxIDY1Ljc0NjEgMTc3LjI1OCA2NS44OTQ1QzE3Ni45NjkgNjYuMDQzIDE3Ni42MjcgNjYuMTE3MiAxNzYuMjMyIDY2LjExNzJDMTc1Ljg0NiA2Ni4xMTcyIDE3NS40OTYgNjYuMDM3MSAxNzUuMTg0IDY1Ljg3N0MxNzQuODc1IDY1LjcxNjggMTc0LjYwOSA2NS40OTIyIDE3NC4zODcgNjUuMjAzMUMxNzQuMTY4IDY0LjkxNDEgMTc0IDY0LjU3NDIgMTczLjg4MyA2NC4xODM2QzE3My43NjYgNjMuNzg5MSAxNzMuNzA3IDYzLjM2MTMgMTczLjcwNyA2Mi45MDA0Wk0xNzUuMTE5IDYyLjc3NzNWNjIuOTAwNEMxNzUuMTE5IDYzLjE4OTUgMTc1LjE0NiA2My40NTkgMTc1LjIwMSA2My43MDlDMTc1LjI2IDYzLjk1OSAxNzUuMzQ4IDY0LjE3OTcgMTc1LjQ2NSA2NC4zNzExQzE3NS41ODYgNjQuNTU4NiAxNzUuNzM4IDY0LjcwNyAxNzUuOTIyIDY0LjgxNjRDMTc2LjEwOSA2NC45MjE5IDE3Ni4zMyA2NC45NzQ2IDE3Ni41ODQgNjQuOTc0NkMxNzYuOTE2IDY0Ljk3NDYgMTc3LjE4OCA2NC45MDQzIDE3Ny4zOTggNjQuNzYzN0MxNzcuNjEzIDY0LjYyMyAxNzcuNzc3IDY0LjQzMzYgMTc3Ljg5MSA2NC4xOTUzQzE3OC4wMDggNjMuOTUzMSAxNzguMDkgNjMuNjgzNiAxNzguMTM3IDYzLjM4NjdWNjIuMzI2MkMxNzguMTEzIDYyLjA5NTcgMTc4LjA2NCA2MS44ODA5IDE3Ny45OSA2MS42ODE2QzE3Ny45MiA2MS40ODI0IDE3Ny44MjQgNjEuMzA4NiAxNzcuNzAzIDYxLjE2MDJDMTc3LjU4MiA2MS4wMDc4IDE3Ny40MyA2MC44OTA2IDE3Ny4yNDYgNjAuODA4NkMxNzcuMDYyIDYwLjcyMjcgMTc2Ljg0NiA2MC42Nzk3IDE3Ni41OTYgNjAuNjc5N0MxNzYuMzQyIDYwLjY3OTcgMTc2LjEyMSA2MC43MzQ0IDE3NS45MzQgNjAuODQzOEMxNzUuNzQ2IDYwLjk1MzEgMTc1LjU5MiA2MS4xMDM1IDE3NS40NzEgNjEuMjk0OUMxNzUuMzU0IDYxLjQ4NjMgMTc1LjI2NiA2MS43MDkgMTc1LjIwNyA2MS45NjI5QzE3NS4xNDggNjIuMjE2OCAxNzUuMTE5IDYyLjQ4ODMgMTc1LjExOSA2Mi43NzczWk0xODAuNzQyIDYyLjkwMDRWNjIuNzY1NkMxODAuNzQyIDYyLjMwODYgMTgwLjgwOSA2MS44ODQ4IDE4MC45NDEgNjEuNDk0MUMxODEuMDc0IDYxLjA5OTYgMTgxLjI2NiA2MC43NTc4IDE4MS41MTYgNjAuNDY4OEMxODEuNzcgNjAuMTc1OCAxODIuMDc4IDU5Ljk0OTIgMTgyLjQ0MSA1OS43ODkxQzE4Mi44MDkgNTkuNjI1IDE4My4yMjMgNTkuNTQzIDE4My42ODQgNTkuNTQzQzE4NC4xNDggNTkuNTQzIDE4NC41NjIgNTkuNjI1IDE4NC45MjYgNTkuNzg5MUMxODUuMjkzIDU5Ljk0OTIgMTg1LjYwNCA2MC4xNzU4IDE4NS44NTcgNjAuNDY4OEMxODYuMTExIDYwLjc1NzggMTg2LjMwNSA2MS4wOTk2IDE4Ni40MzggNjEuNDk0MUMxODYuNTcgNjEuODg0OCAxODYuNjM3IDYyLjMwODYgMTg2LjYzNyA2Mi43NjU2VjYyLjkwMDRDMTg2LjYzNyA2My4zNTc0IDE4Ni41NyA2My43ODEyIDE4Ni40MzggNjQuMTcxOUMxODYuMzA1IDY0LjU2MjUgMTg2LjExMSA2NC45MDQzIDE4NS44NTcgNjUuMTk3M0MxODUuNjA0IDY1LjQ4NjMgMTg1LjI5NSA2NS43MTI5IDE4NC45MzIgNjUuODc3QzE4NC41NjggNjYuMDM3MSAxODQuMTU2IDY2LjExNzIgMTgzLjY5NSA2Ni4xMTcyQzE4My4yMyA2Ni4xMTcyIDE4Mi44MTQgNjYuMDM3MSAxODIuNDQ3IDY1Ljg3N0MxODIuMDg0IDY1LjcxMjkgMTgxLjc3NSA2NS40ODYzIDE4MS41MjEgNjUuMTk3M0MxODEuMjY4IDY0LjkwNDMgMTgxLjA3NCA2NC41NjI1IDE4MC45NDEgNjQuMTcxOUMxODAuODA5IDYzLjc4MTIgMTgwLjc0MiA2My4zNTc0IDE4MC43NDIgNjIuOTAwNFpNMTgyLjE1NCA2Mi43NjU2VjYyLjkwMDRDMTgyLjE1NCA2My4xODU1IDE4Mi4xODQgNjMuNDU1MSAxODIuMjQyIDYzLjcwOUMxODIuMzAxIDYzLjk2MjkgMTgyLjM5MyA2NC4xODU1IDE4Mi41MTggNjQuMzc3QzE4Mi42NDMgNjQuNTY4NCAxODIuODAzIDY0LjcxODggMTgyLjk5OCA2NC44MjgxQzE4My4xOTMgNjQuOTM3NSAxODMuNDI2IDY0Ljk5MjIgMTgzLjY5NSA2NC45OTIyQzE4My45NTcgNjQuOTkyMiAxODQuMTg0IDY0LjkzNzUgMTg0LjM3NSA2NC44MjgxQzE4NC41NyA2NC43MTg4IDE4NC43MyA2NC41Njg0IDE4NC44NTUgNjQuMzc3QzE4NC45OCA2NC4xODU1IDE4NS4wNzIgNjMuOTYyOSAxODUuMTMxIDYzLjcwOUMxODUuMTkzIDYzLjQ1NTEgMTg1LjIyNSA2My4xODU1IDE4NS4yMjUgNjIuOTAwNFY2Mi43NjU2QzE4NS4yMjUgNjIuNDg0NCAxODUuMTkzIDYyLjIxODggMTg1LjEzMSA2MS45Njg4QzE4NS4wNzIgNjEuNzE0OCAxODQuOTc5IDYxLjQ5MDIgMTg0Ljg1IDYxLjI5NDlDMTg0LjcyNSA2MS4wOTk2IDE4NC41NjQgNjAuOTQ3MyAxODQuMzY5IDYwLjgzNzlDMTg0LjE3OCA2MC43MjQ2IDE4My45NDkgNjAuNjY4IDE4My42ODQgNjAuNjY4QzE4My40MTggNjAuNjY4IDE4My4xODggNjAuNzI0NiAxODIuOTkyIDYwLjgzNzlDMTgyLjgwMSA2MC45NDczIDE4Mi42NDMgNjEuMDk5NiAxODIuNTE4IDYxLjI5NDlDMTgyLjM5MyA2MS40OTAyIDE4Mi4zMDEgNjEuNzE0OCAxODIuMjQyIDYxLjk2ODhDMTgyLjE4NCA2Mi4yMTg4IDE4Mi4xNTQgNjIuNDg0NCAxODIuMTU0IDYyLjc2NTZaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjM4Ii8+CjxwYXRoIGQ9Ik0yODMuNTc0IDYzLjEyNVY2OEgyNTguNzkzVjYzLjgxMDVMMjcwLjgyOCA1MC42ODM2QzI3Mi4xNDggNDkuMTk0IDI3My4xODkgNDcuOTA3NiAyNzMuOTUxIDQ2LjgyNDJDMjc0LjcxMyA0NS43NDA5IDI3NS4yNDYgNDQuNzY3NiAyNzUuNTUxIDQzLjkwNDNDMjc1Ljg3MiA0My4wMjQxIDI3Ni4wMzMgNDIuMTY5MyAyNzYuMDMzIDQxLjMzOThDMjc2LjAzMyA0MC4xNzE5IDI3NS44MTMgMzkuMTQ3OCAyNzUuMzczIDM4LjI2NzZDMjc0Ljk1IDM3LjM3MDQgMjc0LjMyNCAzNi42NjggMjczLjQ5NCAzNi4xNjAyQzI3Mi42NjUgMzUuNjM1NCAyNzEuNjU4IDM1LjM3MyAyNzAuNDczIDM1LjM3M0MyNjkuMTAyIDM1LjM3MyAyNjcuOTUxIDM1LjY2OTMgMjY3LjAyIDM2LjI2MTdDMjY2LjA4OSAzNi44NTQyIDI2NS4zODYgMzcuNjc1MSAyNjQuOTEyIDM4LjcyNDZDMjY0LjQzOCAzOS43NTcyIDI2NC4yMDEgNDAuOTQyMSAyNjQuMjAxIDQyLjI3OTNIMjU4LjA4MkMyNTguMDgyIDQwLjEyOTYgMjU4LjU3MyAzOC4xNjYgMjU5LjU1NSAzNi4zODg3QzI2MC41MzYgMzQuNTk0NCAyNjEuOTU4IDMzLjE3MjUgMjYzLjgyIDMyLjEyM0MyNjUuNjgyIDMxLjA1NjYgMjY3LjkyNSAzMC41MjM0IDI3MC41NDkgMzAuNTIzNEMyNzMuMDIgMzAuNTIzNCAyNzUuMTE5IDMwLjkzODIgMjc2Ljg0NiAzMS43Njc2QzI3OC41NzIgMzIuNTk3IDI3OS44ODQgMzMuNzczNCAyODAuNzgxIDM1LjI5NjlDMjgxLjY5NSAzNi44MjAzIDI4Mi4xNTIgMzguNjIzIDI4Mi4xNTIgNDAuNzA1MUMyODIuMTUyIDQxLjg1NjEgMjgxLjk2NiA0Mi45OTg3IDI4MS41OTQgNDQuMTMyOEMyODEuMjIxIDQ1LjI2NjkgMjgwLjY4OCA0Ni40MDEgMjc5Ljk5NCA0Ny41MzUyQzI3OS4zMTcgNDguNjUyMyAyNzguNTEzIDQ5Ljc3OCAyNzcuNTgyIDUwLjkxMjFDMjc2LjY1MSA1Mi4wMjkzIDI3NS42MjcgNTMuMTYzNCAyNzQuNTEgNTQuMzE0NUwyNjYuNTEyIDYzLjEyNUgyODMuNTc0Wk0zMTIuMjE5IDYzLjEyNVY2OEgyODcuNDM4VjYzLjgxMDVMMjk5LjQ3MyA1MC42ODM2QzMwMC43OTMgNDkuMTk0IDMwMS44MzQgNDcuOTA3NiAzMDIuNTk2IDQ2LjgyNDJDMzAzLjM1OCA0NS43NDA5IDMwMy44OTEgNDQuNzY3NiAzMDQuMTk1IDQzLjkwNDNDMzA0LjUxNyA0My4wMjQxIDMwNC42NzggNDIuMTY5MyAzMDQuNjc4IDQxLjMzOThDMzA0LjY3OCA0MC4xNzE5IDMwNC40NTggMzkuMTQ3OCAzMDQuMDE4IDM4LjI2NzZDMzAzLjU5NSAzNy4zNzA0IDMwMi45NjggMzYuNjY4IDMwMi4xMzkgMzYuMTYwMkMzMDEuMzA5IDM1LjYzNTQgMzAwLjMwMiAzNS4zNzMgMjk5LjExNyAzNS4zNzNDMjk3Ljc0NiAzNS4zNzMgMjk2LjU5NSAzNS42NjkzIDI5NS42NjQgMzYuMjYxN0MyOTQuNzMzIDM2Ljg1NDIgMjk0LjAzMSAzNy42NzUxIDI5My41NTcgMzguNzI0NkMyOTMuMDgzIDM5Ljc1NzIgMjkyLjg0NiA0MC45NDIxIDI5Mi44NDYgNDIuMjc5M0gyODYuNzI3QzI4Ni43MjcgNDAuMTI5NiAyODcuMjE4IDM4LjE2NiAyODguMTk5IDM2LjM4ODdDMjg5LjE4MSAzNC41OTQ0IDI5MC42MDMgMzMuMTcyNSAyOTIuNDY1IDMyLjEyM0MyOTQuMzI3IDMxLjA1NjYgMjk2LjU3IDMwLjUyMzQgMjk5LjE5NCAzMC41MjM0QzMwMS42NjUgMzAuNTIzNCAzMDMuNzY0IDMwLjkzODIgMzA1LjQ5IDMxLjc2NzZDMzA3LjIxNyAzMi41OTcgMzA4LjUyOSAzMy43NzM0IDMwOS40MjYgMzUuMjk2OUMzMTAuMzQgMzYuODIwMyAzMTAuNzk3IDM4LjYyMyAzMTAuNzk3IDQwLjcwNTFDMzEwLjc5NyA0MS44NTYxIDMxMC42MTEgNDIuOTk4NyAzMTAuMjM4IDQ0LjEzMjhDMzA5Ljg2NiA0NS4yNjY5IDMwOS4zMzMgNDYuNDAxIDMwOC42MzkgNDcuNTM1MkMzMDcuOTYyIDQ4LjY1MjMgMzA3LjE1OCA0OS43NzggMzA2LjIyNyA1MC45MTIxQzMwNS4yOTYgNTIuMDI5MyAzMDQuMjcyIDUzLjE2MzQgMzAzLjE1NCA1NC4zMTQ1TDI5NS4xNTYgNjMuMTI1SDMxMi4yMTlaTTMxNi41NjUgMzcuMzAyN0MzMTYuNTY1IDM2LjA2NzEgMzE2Ljg2OSAzNC45MzI5IDMxNy40NzkgMzMuOTAwNEMzMTguMDg4IDMyLjg2NzggMzE4LjkwMSAzMi4wNDY5IDMxOS45MTYgMzEuNDM3NUMzMjAuOTQ5IDMwLjgxMTIgMzIyLjA2NiAzMC40OTggMzIzLjI2OCAzMC40OThDMzI0LjQ4NyAzMC40OTggMzI1LjU5NSAzMC44MTEyIDMyNi41OTQgMzEuNDM3NUMzMjcuNTkzIDMyLjA0NjkgMzI4LjM4OCAzMi44Njc4IDMyOC45ODEgMzMuOTAwNEMzMjkuNTkgMzQuOTMyOSAzMjkuODk1IDM2LjA2NzEgMzI5Ljg5NSAzNy4zMDI3QzMyOS44OTUgMzguNTM4NCAzMjkuNTkgMzkuNjcyNSAzMjguOTgxIDQwLjcwNTFDMzI4LjM4OCA0MS43MjA3IDMyNy41OTMgNDIuNTI0NyAzMjYuNTk0IDQzLjExNzJDMzI1LjU5NSA0My43MDk2IDMyNC40ODcgNDQuMDA1OSAzMjMuMjY4IDQ0LjAwNTlDMzIyLjA2NiA0NC4wMDU5IDMyMC45NDkgNDMuNzA5NiAzMTkuOTE2IDQzLjExNzJDMzE4LjkwMSA0Mi41MjQ3IDMxOC4wODggNDEuNzIwNyAzMTcuNDc5IDQwLjcwNTFDMzE2Ljg2OSAzOS42NzI1IDMxNi41NjUgMzguNTM4NCAzMTYuNTY1IDM3LjMwMjdaTTMxOS45OTMgMzcuMzAyN0MzMTkuOTkzIDM4LjIxNjggMzIwLjMxNCAzOC45ODcgMzIwLjk1NyAzOS42MTMzQzMyMS42MDEgNDAuMjIyNyAzMjIuMzcxIDQwLjUyNzMgMzIzLjI2OCA0MC41MjczQzMyNC4xNjUgNDAuNTI3MyAzMjQuOTE4IDQwLjIyMjcgMzI1LjUyOCAzOS42MTMzQzMyNi4xMzcgMzkuMDAzOSAzMjYuNDQyIDM4LjIzMzcgMzI2LjQ0MiAzNy4zMDI3QzMyNi40NDIgMzYuMzU0OCAzMjYuMTM3IDM1LjU2NzcgMzI1LjUyOCAzNC45NDE0QzMyNC45MTggMzQuMzE1MSAzMjQuMTY1IDM0LjAwMiAzMjMuMjY4IDM0LjAwMkMzMjIuMzcxIDM0LjAwMiAzMjEuNjAxIDM0LjMxNTEgMzIwLjk1NyAzNC45NDE0QzMyMC4zMTQgMzUuNTY3NyAzMTkuOTkzIDM2LjM1NDggMzE5Ljk5MyAzNy4zMDI3Wk0zNTcuODc5IDU1Ljk2NDhIMzY0LjIyN0MzNjQuMDI0IDU4LjM4NTQgMzYzLjM0NyA2MC41NDM2IDM2Mi4xOTYgNjIuNDM5NUMzNjEuMDQ1IDY0LjMxODQgMzU5LjQyOCA2NS43OTk1IDM1Ny4zNDYgNjYuODgyOEMzNTUuMjY0IDY3Ljk2NjEgMzUyLjczNCA2OC41MDc4IDM0OS43NTQgNjguNTA3OEMzNDcuNDY5IDY4LjUwNzggMzQ1LjQxMyA2OC4xMDE2IDM0My41ODQgNjcuMjg5MUMzNDEuNzU2IDY2LjQ1OTYgMzQwLjE5MSA2NS4yOTE3IDMzOC44ODcgNjMuNzg1MkMzMzcuNTg0IDYyLjI2MTcgMzM2LjU4NSA2MC40MjUxIDMzNS44OTEgNTguMjc1NEMzMzUuMjE0IDU2LjEyNTcgMzM0Ljg3NSA1My43MjIgMzM0Ljg3NSA1MS4wNjQ1VjQ3Ljk5MjJDMzM0Ljg3NSA0NS4zMzQ2IDMzNS4yMjIgNDIuOTMxIDMzNS45MTYgNDAuNzgxMkMzMzYuNjI3IDM4LjYzMTUgMzM3LjY0MyAzNi43OTQ5IDMzOC45NjMgMzUuMjcxNUMzNDAuMjg0IDMzLjczMTEgMzQxLjg2NiAzMi41NTQ3IDM0My43MTEgMzEuNzQyMkMzNDUuNTczIDMwLjkyOTcgMzQ3LjY2NCAzMC41MjM0IDM0OS45ODMgMzAuNTIzNEMzNTIuOTI4IDMwLjUyMzQgMzU1LjQxNiAzMS4wNjUxIDM1Ny40NDggMzIuMTQ4NEMzNTkuNDc5IDMzLjIzMTggMzYxLjA1MyAzNC43Mjk4IDM2Mi4xNyAzNi42NDI2QzM2My4zMDUgMzguNTU1MyAzNjMuOTk5IDQwLjc0NzQgMzY0LjI1MiA0My4yMTg4SDM1Ny45MDVDMzU3LjczNSA0MS42Mjc2IDM1Ny4zNjMgNDAuMjY1IDM1Ni43ODggMzkuMTMwOUMzNTYuMjI5IDM3Ljk5NjcgMzU1LjQgMzcuMTMzNSAzNTQuMjk5IDM2LjU0MUMzNTMuMTk5IDM1LjkzMTYgMzUxLjc2IDM1LjYyNyAzNDkuOTgzIDM1LjYyN0MzNDguNTI3IDM1LjYyNyAzNDcuMjU4IDM1Ljg5NzggMzQ2LjE3NCAzNi40Mzk1QzM0NS4wOTEgMzYuOTgxMSAzNDQuMTg1IDM3Ljc3NjcgMzQzLjQ1NyAzOC44MjYyQzM0Mi43MyAzOS44NzU3IDM0Mi4xOCA0MS4xNzA2IDM0MS44MDcgNDIuNzEwOUMzNDEuNDUyIDQ0LjIzNDQgMzQxLjI3NCA0NS45Nzc5IDM0MS4yNzQgNDcuOTQxNFY1MS4wNjQ1QzM0MS4yNzQgNTIuOTI2NCAzNDEuNDM1IDU0LjYxOTEgMzQxLjc1NiA1Ni4xNDI2QzM0Mi4wOTUgNTcuNjQ5MSAzNDIuNjAzIDU4Ljk0NCAzNDMuMjggNjAuMDI3M0MzNDMuOTc0IDYxLjExMDcgMzQ0Ljg1NCA2MS45NDg2IDM0NS45MiA2Mi41NDFDMzQ2Ljk4NyA2My4xMzM1IDM0OC4yNjUgNjMuNDI5NyAzNDkuNzU0IDYzLjQyOTdDMzUxLjU2NiA2My40Mjk3IDM1My4wMyA2My4xNDE5IDM1NC4xNDcgNjIuNTY2NEMzNTUuMjgxIDYxLjk5MDkgMzU2LjEzNiA2MS4xNTMgMzU2LjcxMSA2MC4wNTI3QzM1Ny4zMDQgNTguOTM1NSAzNTcuNjkzIDU3LjU3MjkgMzU3Ljg3OSA1NS45NjQ4WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC44NyIvPgo8L2c+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfMTI0Nl80NDQ0NyIgeD0iMCIgeT0iMCIgd2lkdGg9IjM5OSIgaGVpZ2h0PSIxMDgiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldCBkeT0iNCIvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSI0Ii8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4wNCAwIi8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0iZWZmZWN0MV9kcm9wU2hhZG93XzEyNDZfNDQ0NDciLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3dfMTI0Nl80NDQ0NyIgcmVzdWx0PSJzaGFwZSIvPgo8L2ZpbHRlcj4KPC9kZWZzPgo8L3N2Zz4K", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAAA6CAYAAAD1AhaMAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAADi3SURBVHgB7X15uCVVde9vV53pDj030PQATTODSLQBEYxC1KCoIF8Cgg9Bo/gE1DgkTvniR+J7CRmMyQMkUQyivkRNvoBG9CUCikYjIMhMMzXNcHu4Pd/xTFX7rbX2WHXqNOSP/Nd1u/rU2bWHtdde015r732A/df+a/+1/9p/7b/2X/uv/dd/x6W1VvbTPcsdPQ9LH7hL7/ZV5z7vUhkPp7tdHvuuUKb0Xl111VVJXP4lwoJyfS59yLOHJYYphsO9i5/LeKron3qx/pXSEH2vghUVuPXv4z5U4acEI/DiYwkMjmeBPlyaHacCLFXtDMEJoutF4S3je0g9poJjz/zUofR5mFJKOwTyc1SpyWzfx1df8veUykx+pXL6yOkz0ej30extVmctfhZ3zL5CT6ejaiSb0ut6/6mexvHoqFGNGpdPqJ0a6nX6TjXqNFF11dDFVmowsFHd9UTe1fz7tAgUv+hnUq7fbqPd3oPe/LR6xcg2ffgxx+DprW2sa+5W3922RHPVvd60es/Fb9LHH3MEDORaUzGqp4YNW2/C/NxuNTnZ0ZseoLzUV9cI4+Nd579LHXLIIZrzZp2O4lYbaVrAUzfL1KOPPam/9Z0H0O7XqL051dm7Ua8/7hB1+RVXIOX8tVoBq7Wod1RcURad0YOmB0WfXEa+0/XTn/5c/cO3b9arX/YmECppCBLdp/7rXlfV+jM4oXO3Xr+6jR8kZ6tdcw1CYVcdPfEdLKjP6sVo4e1ba/jKr/0aNmEcqzf8Eq9fsRa3v+ZczPRaGNVzKh9ZKPDlVHcjaWoGlZ9r9FBvmO91HgWbJ0lS3ZvrqKmpeZ00lDp2+7/qU1//a7i/dgbmfv5ddfzaGTyz8hLt8Oi6nmd9wW2S1rTrPafl9F0+iaa7nb7aNd3W9/7HL7CQ+nf5ZWfr1avHPbb6/T7DVcB/nMbPT72wVf/4tqfw7otPVa1WSwesRzSl1J4l4+MP1I4681NfILL/XWaswDwKeT7AC0CJuxy3KS5Lf8RuyGGlfZbJ8wJkqNH4a5VTYo6kP0XIVJSqTN4sAWFU7izvgZCLRNOz7hOMiQUnkdyUQI+EQy2PlMItJ0yotk9K5IPOOW9KfSAiYdgYs/Q5WicU5Aq17gym0oSQz30mODMG3dRP40vlqBKCp5ftRbc7h16/R4xGSSM5RtOatM29nNuTYa4zT7Bq5jIpw6yalfCUUnpOwoLImcpmLJUYQWiONPD85s0BdotL80/BdkfaElxTP7huTkuUeZNlfTz++KNSst+julOCQHGf+pQ9R70/S4w7S0w4CkVM06W73t6FNYspf17HaK2F3vP0nsZnZr5NRTP09+zA4qmncdIJh2Hp/T/Ez0ffjJ35IsqTgkVmTuORyJjQTTARQ0LX6B31s07ULt2jccqpzlrSIFgygpuRlqCZT2F8ZIHAm/BYMhVYOZykicWFoyOWm4mMDY+7onyKx41oo93uYSG116orgSG3OEzTWplMfRrjkOlr73bGR04M3SjQeaoUdETrO/dO/w2xvP7dClaQyrhB91lOs7l8ujCIgGn4SNnXylEzAyh/uSFaRw6KB5T5NiUkpMIUjjEMwasCA5gkmw5D0MI0UBGcuYGR5FBu65N2tCljuEhHXdDmux2TLDOdmO/uQKfbFyR2e7l0j8abpCYxeJ+UT09j1+7dkaCIsQLfZ487YRYarHqD4E5x11134d5777WZVcQMRTyawc3lrcctY9F2o0PqbnzJIQRn7m0LFhIsGDJizHavS7lH0Cdm7rf7aPXnceCyFFnaRGvxQajtnMbsjh3oJQuQ1JiZu/jVT36A157+fjS33YuRA86kdhZaKAO1xOMjN1hGJBYfyuNE+x5KIRl/VbBNYL8bUasKWHR1BRoMGNCWDoKAecmXjmy5IWUp+XdrZZurUIex0wppPAi+hWjwfYtmNMOASkswjCGECyMdtNArHGHAMp620lmYRFkpY5nBI1EZrSFSRdnvAUK6E3g1Y5mD8wiUVko4vij3l43Inz22ERNPPUWaY5qkaoOkFWminDSbqqM/n4LNL+4eWYy4d3MHG3/+OHLSIBkRliZtI4xOhLKwVcdZJx9DGtQQTb0xirS1VAqPLVqOrNe2wiXqL8ObWLhZm9rbKhaLC8Jm1pM2yfYgSVgn6Vs3fchyknmM5FzGKqfv/dyMSZaTydnntC7JJJa+xFyzHeQjTcxun8TcWJdNTJIQJF1JSqf1Om7oHI0lXUVSv4eREdI44000G00Wt5hta3QyLTgV2Ke3Y3z5MswTE2rWpRa/qiQ6AhPYby5fEgmTEtH6MlbyWjbDf/VyjBx/39dVq0osaoiyxtCV+bSVwip3DGLfa4eM3DKcWArCJO49c4Wov8DTlmBYmveEsZqNmkj2rN9BvUnmAkm/wc6VKN4Sm5hwysz/gizy8tpLeVfFPBHy1oyYYZLMkyzF/HwNjUadYCCVnCS+rFq+EtPpOKZ3zCDrttGfmzW1WQJddeBiySeESp1mG701OgbWlmz+GWHjqEP525mNBm77mVhtyf2wxE4cTATfpbZ6oi2kvtzimbVJbrW6Gw9mqj5pCDbzWsS0pBVnZ6apmiYWkWbLCbc5M2dzBAmZMO1tO7CbzKcTO5tw1Ggdi486FSvWrcLCxYsJHw18/54X8PCze70o3jvXweiuSahFK0hQZFYwWmVtJaWKhskwAqwFoC2jaCtnI7PZjpfWFUOM0vcKgvca3r2r4InYMorrqBlJP4zQKiACClolPOfWUtFB8zhmgdUl2kg30UHWDFLWPFPWdnDaiD/7WQd7djxKdvEeHLx8BAc2G9hIRLvkkCOwfXdCgzSKg1euxNQs2fe5CoabMnMi6afVNkhixR1JETeCUTcPXjiKJ1asMQy6eZNHarJoGfLpPabswmWoHbAaWXvOzA26HSH6ZquBbrsrw3zAogXWPrb4EGJgMWBNyTRAtGAkxaLxOkn1VIRBs1kThmw062iQJOcJMTMYzzF4yNrUxq7dM3jkqS1k6nXo7optGIROZrVTbu15o9W5rE6NJk8bKc2jptHpJVhE7+v1Grp790KPLRStMDk7jT1kQm7uNXHY1B5M7qa+zmYYWUSah0zMxzbtorppvpixba/RIPy8sOVpLF6wCmbKG1MgIsvWwOMsAWc3FgSX8iZGqEJFdpEzQdSLa4F4iuCKlum6rLXcd3X4az42wAFrVi7DJ694C0ZbTez7suZBgb7cl5zlJGokrVIZpAR9sZ8yQ7gksUiH04DXyDxgk8QAlZPkYbuZJ58iJTVN3Hmyp8wkKrXzFDajmNjIiyLqvEcMwgMF7aSy1Va5IRayuFFXfJu3PGnsZoxjrqOO8fEWRlotIkLKyVqEJrx8952JAivgnfbw3c0DUdr+12TymKJF9fIzS/Z5Iuh2J6OJP/NTbGDwPFQRsxNjUJl6PRGTjCf2jJeUn+k2plYiNxfKCci5Tg/TZCLNzXdJC2QyGdZWg7AmUZq9YT3qc48tIhkDnqUkBHODcUE4SdmBQXWR5UUeOiVEx5NlNsGQGhzn1HaPzMtejeihTjiqs4+N+plzjVrqZobKkdt5iJKxkv5RWzz3dn2QVPrMaTKnU7pVOkBVxTmICnqehSYB2usa87KesunXwNhokwRI4ueo5VmDS+NxYO9et2eYt05jzYKHNVin2zWmaaGcKppYjsMuOvfUSuYwBlf8DYEf4rQqrVNRR8GMcIIjUd7OdD0drC2kunlLhE9UFioCH7JU5HdqX+Y/ObztqwZbjzRQ0F5+XuT7FfXDaitdqEthQKIV+ojKq4BHl8LPZZxYbaq8prSWgekeVJjgBEeIb8NodxZSir2CLLT6opiF0RLrSxQNa0FIYOeM0VwhEL1Njz1GQDS/KPVOuzLWYYQhSIiTojRdGNdqTcOmK2vpNjFesawuMkjRQ1WkqTJzFAetWnVVDXmhE24wEuuJYjconLcm9xP9AvI8ICZPxhMTo5L8e+eGhStXRWBV+WxbPMAs1fnOWDJa7RYY11brjGJnJsIg25VNVCALbR0XucwPghcPjoF0qHvYGBRSPP6s7RI9a9cxJ3TkzxCzTKYtgYo2gQAtWVPHZtYBkhi/gbiASeeKe5Vd4ylxSD2XmZSvi1242nJIAhOZUrY/7LZPPJqTqB/FXrkxMQnuS8k8cu+ign4OqQbxFWuUIXImqkrZZrVFL41j2T6LuUwNqUZHkqkSaA95AHxA+vJ/MlFN7HvjeTImQl5gEImvaCN5EivRDE1ltn4tZlbsLXESdYA/XELBxrV9sqKTCZtjNTn73O1EN8sjHah00BqWSQQ2NtfYXBLVnQiBOYJ3ZpjEZPyAmeGUvsJMvs28wQKpw0fAXFIStM6bl4hUD048beIGVqJLUoJItkNwlteciRx0BqzJZPJrG98hnCgzo6GJK303Y+FdFlJhUXS7b27MImMJ5WtAuEcTaj9UQyl83zQbpjlFxhrMx6XzQEVEizXnElVeo+kXAaF0RVojNjtUMQOCsHUSt2/aFUaoGf85JHIqapwDeIh8/xI9YW9XYohIPEPizSEplnAgqQYfWITjA+Wle+I6HisCHQAN75SVmuQEsOZxorQE5oVh3c1MaQlR4isyP0pkossMUremY8HTVzU+XlKWpKIuYdWaGvAMFIg9mEbKOCMyZbUCManmOVsu5iLPJYQpLKkEqe40ndMgJj13Gl6aMGyWWtPRTldERKmQCU6MZzbYx7hLrF7WdlJdRWEeDXGChS4iscpLRRZMzKMeq/516WVh8FUhB1smbfLn17yKhg5ut+Hs4BwOQ02Y4lddGGR5r41EZn89+/DZ1y8el8QMlzAITdJVnnuJE/XfmyrCJDwp5Zh8mgkDJM7u92hzz8a8SBBb+0GSBgidNjDmRc3mSXmKnxim1HmsOfhKrBRPJQhYk4l1GplX2pGwJTaFiC8LbkxEQsq5akWSq9xEsHNDALLqIMt9FFgVzC3qpXLaw9yJTBiYaepE9OQu1wYTavgwm/osfIV5VaLshDz0xxGYf/aGA0tgM44hAOzordScQ78qpPjxKZv/PkdM6IUCjjlCwr6EvPGQWeHO/RMnRyEO4jwAeh/qAoHptPkSq8tC3yIzzKk332X2ZMGYTUzsyjOINt4X0iBK1Hiowwy4M1VcPRb5PInkOENip4c6DJpT6oZBjLlh6CrYwrqym0poipc2yPIThpWXoPCyCue5kow2EJmYzyRRKAfGDPFHE+CkgC3PJOw9S3IHTx5wKbjSxmSTgcyFWfq8hCOPYHFS0Ep3x5jibGDtwbEOJlnrOdqHUA5wRUwWM4t5b9r4+X/8RO5tW7dI+kErVuLEV6zHWW8+W5iEPYi513cKmyc24x+//U/YvGUrZqZnpMzrznwDXvcbb8CKlatCt+11w/XX4L5f3i3PH/zoJ3Do2sMr4eWVCbfddhs2b57A1NSUpB17zLG45NJLsWr1KuwrKO76K0aWNgFWtmrUka/9RKlUjt//wNk4et3B+6iqZA7Q3/RMHVPTNaxaMQdnI/CKK3Yl1nhAYcIiYp7IGinnnU/ExlWO2awJoz3zBa8K36md/LpOiCVtJ8WpGzxvLwYglSUwR+zs0uxSdJyJpcZu3rERjFJEmU0kF6R0jOgkkbbaz0huHSll5eH0BXXQeOyCbZNLlt28/ZKbF7ZPNXLx1lLTN9NHdo0qk2bXqmnL4s6a75HLs0t+Y/7MrclolpjkYqYmJHga5OJtcmCQtQ65VruqISSrrYAr8rKOWTpoEe9pNPMcZfExuW0zPv/n/wsP3n9fXMpfv3X+RfjQhz+CRkI0UDP9+OY3v4kv/NVfGUdCKf/BxBx//tfX4cijj/VA/fVf/Cm++Y2bJG1meoqYawJfuulbWHfYOuPmHW3QuDXwt9d/Eddec81Q4f7BD30IH7zygwU3b41NYYkvWVc8uY6NAM0llsV1pcsOPe0q21+vQU4/+SgcsHQBhl9FKO5+4AB8/8drsGHjEuzYPYIj106JpDPDmcMtAg5jor3Nqj3GY0JLjGROwjoqtxTDm4TaDKCXbExAyhCR9kRktEWuXbvBZpcAl5gjSvzzdSFQowFihhCFlTungLZMrgL8dm2Mi7nInZlJvfmeme+SBmuHG4HgJtgmCGTqyn09xgTNrSnZz5jRNLp9bRmDPqXO3MIUa1arXbWLReWCP83mFTOHNcWKly6ZJKXn4F4yDEhR+c//2edw7z13SepZb34LfpsY4sRXnoTnnttEEfoZPPbowzL269e/UvB6663fx9VXXy0jcPCq1bjsA1fijW9+KxYsWIAnH99ADDCNX/zsp7jo4kslzwxF+T/xkSvxlnPOwxe++GWc/bbzcMs/f4vy7cXprzlDBEmDxu1LX7oeX7z2WoFj4cKFuPCii3AR3atWrcKOHTswPTWNu++6G+PUzgkvf7nEkIT4bVwpsS7nTBufvtJhSVWtOIlUHtGDblw1lFeYQdatmcbC8R7uf2wpMUkLy5fMW2FqCQnKeiLMSlsejjvu/DF+/OM78Cgh8rjjTsD5F1yI444/oaj6I2XAWiDzplVwLnAHM0f+zg53NGOJJZHFU8CXb7wR7/+d93pp7CQyBxlZGou3TGVR42EeZeYfhlYSu+REB1Xhn8NqAPOc2aUmxqZ3XqhY4xhNlFkzWBYTw8w5EtYEypiXhtwRG6sV4xR/li/bE+3mRkUT2fRhoFJzcd+dV45geuj+e/Cf//lTgeSss96ET33ykxL8ZcP49Ne8Dv/zdy4WAv+Pn/4El733vVLF9773PflkQr3+Szfg4NVrRagxA/B163dvxhYyj+795V1Yf9KrfDeOIu3B48rl+BazzL7bsnkzrr/uWsEjM8S/3PIdLFq0UPKfp8/DJZdcikve9S5MTLyA6669Bueccw6avFQJxTiMMW6UFeaB1mtuIG0WR8oITPFiSDfXukOmMbF1TJ473Sg6WpiYWIJTRpKy3fnaM9+ID13xPvzhH/2pdOqfvv0PeHbTRvz2Be/E7Ows7vzRbZibm8VJJ5+KRx95kBjpeKw99FDceeeP5P2rXnUKTjnlFHyfkP/II4+Q9OByc2KPHrr2MBqkGWx6ZiMOP2yt3D+8/Ud42cteTibhHNa/6nQ8+PDDJMEWYueOSezcuR2nnfYaPPjQ/UQAD+CNNPCvfvXpgUGsq9JMxI1p5MwoZ2rq3M16nOYxzGGYJvFOEO8vsd/DF0O0ua2XnXm8bBxiYkUu0nhoCpf2HwHukF1H0i9YejoaZjv2Lo9WfpmOaDn+JM596IH7fZk3/ebribF7ssiRBc+KA5fTPGQFZp6aFiZx1333mZXLr1y/HqsOXulmWXK9lmiBGYQvZhK+eFxeedIp+PLfXotpMq84ne9Lfucybx3+3d990ddxzXXXkQYZj/oBYZpLLr0Ef/onf0J1TOPmW27Bhe+4KCjbCIa5mVlZwaFkZUNDxigxrstMbmVdq7qM7GGXLmY55cTJIeUiief9244VrWeDJMk9d9+FTZuewW9dcDFuuvEGbN+xHWvXHS5MdM89v6COvg93ksaZIcaYnJzEpTT5uvHGv8fDDz8izHHBOy7E33/lBkxun8TY+Bh+4/VvxPj4ApxByP/X7/0rMcYJGB0bx6mvejUepvxz8208++wmyr8dDzz4INaffBp27Z7Cr+77FX77oktw01dvRLuXo0N3u0tmTTc3q2HtxDj3GkpHhOc8UGFNWogxwZuTzgPkTC2/ZswLEqehUDDzYty/yOhYM9E9RrEYhPmRX1CqzJ04uJTTcFFfjP0nz6e++jR8/GO/h9/7+Edx+Lp1Zj8NBRLZ9EKvS3O6MSnL+OeLzafPfvazcl900YWeQKMPfzFjOJnx2c9dTRrkGNxATHLnj27Hhz/+SZx19rm+3H2/vEeemRGOPfZYOHsF0f2GN7xBhCjfqoREZYUO79fptOexe88Mep02er2OjEtN6SKVD53px5qnqIX8xaZW5VWx8KwiE2ZJU/D17LMbxd6cnZ0hgl6AMSLqsdExeh6Fg3WMnkfHx+UdEzinbtq0CW9929ulngMPXAEmsh/fcRvOfss5EfEFQnVzHeeqHaG2tm6dJO0yi6effgoXvPPdJBQTL4llPhVJ5vipQHgFpMHbtc4E9OpdI5pXRZjwcRGrW3ScP8ZYuEJUH0ELIDCsQmBkbZ0gKtIgfrWw7OeAZ4g8Wh3sF3zS82E0ST5i3VpyjGTiIPHtU5nN5M16gCfuVN+RRxwpZdg0eutb32rNTFXQHpz2E7IUTD6jNdyk+GAi/Ou/8nVyAE3JWPPauF6nK00x023eskXAYgYJ8RBdqHvVqpX42te/Jpo8k0l6hgK+pb88r+qjTQJwnOZ8ikMNDW00SFnSDTg+q6ws7Q0Jn3TKidsHC5WjYwNSIwzzyaechjnSDo8+/BC2T27DIBcaycqD+Oyzz+Hzf/EXMmc55ZTTsZ2YhDXJtslJmKCUmVvMzs3hEZrjzBDRc+mDDjwQt93xI/KCHI7/8zd/iUceerDgMDj+xFcKDA9T+jPPPC0MlCi31BwYCGbJZDiYJS5i7IworyOVi2rHVmfsEnYMFHoaz1GCKrBpESC6JBUL7BFpNbEOchuAZdc6P4tLvc9LScG7UnmxZLOVkp3OHh4XBbdWRm7LwAZLc+3NSJOuJcB71VV/KM+sRd596Xtsezpi3AArX7d+51+8eXXh/7hUNIhZ9uFWFhgGQwF3Wrxarg5mAhW/d+3pwhqBiLZ1hK8AWyOBDYRaYXr4aR/WZUR/8spzccwRK309ulR5bP7y8zVfOw7Ll7YlkSfo5/3mM1h10Iy4GcVvIhNMJQEqE8o3EiyzK3bCYDsvFiLJagc3z/3QP/boQ6Ru78AVH/poIDBvuthIr4M95z0PU1gw1kKdbHleCjI3P4/RBYtl/8LI+GJDwqqAflOlMl44g2QDN0eG62niXbIR8iyuItkYCR2e/BscpOjrEM2O21SRp8is2bQub+1iInb3ZWK8Xi5vGDsj6UXqsy2d96yb19jVPBpdbluHOZAzrXi1bb3ZoLtptIiV1N12z9SHSNNxgFfZ5SaiQSBuam7j77/6DZLW35BsV15xJS44/x2yUrnG7nMXp+IVwmmT4jJ10jZbcckF58kcg928N//gjgGx6MQ2awCnQfJ+Dzu2bcFF558jHrLzzjsPV//Z1YEJ/cCEMQqreTPpi6zmJbg5PNAls3By2w7snOli2cIGWoSLFlkoNY/88qUDWwQgHcDhW7tr3IU7drV8WqebYEil8J4eaO8xMME+Q1xamSUSPqCmTcjMLMAzEdnD1h1JA1qXvcYMoQmWxe1Y2W2lz6jYw5mXqvyd37HKzl32UvEg/XMhAtEMygYb/eS5tEhDmzKBziNjS5l4fmJr7um0IGSCCaX9kHr/mXZtcAMunB659wrNReLLSWDlltqQH0xWXgLefFPOvOL95cosUyFBpojBkn6X8nfp2bKyUr5JF0j1DnUC66av/yO+/o3/K3kueeeF+K23nU1euA7ddRPohTMuDX1wQO+Ky97jmeOLZEqF+svdU6XOYvDSbrNVnEUHYWlNx+CACGYnl6sTp480atKvVrOGMdKiEYPEIBUHKFZHYSTNdfO/r0P5uv3nq3HA2U9i0XiG6iuggCVy6tQ/T3yZK1ItBwQ4BpHto5HhME7zjxNefoKR4PLO7c6zXEWSyVNswJ1840MPUufW2ddltUYCs5dE5L9T136PmbI9CbpAufmGil0T5skVy4RRTHy/qJMtvCrg2g2q5FDabiyzLyQxXthpWdVznvLjpXRuV9maeRSsdjYrLBJD5LLMxzIfSWi+U3YzIyyXMQ8m5sSaILHa7p9v/g6++nXDHOzVevfF7zT448M3ZDWvhIrhlo9u2ULM8f7LxE3LJhUzx8E2il6mklhEx8O2YHzcP5vIeXFyUGAWp2ERvheW89CcI+13sKRJHizSpg3SeDxfqQVgtEWvQth0hIghhFLDM1W+8fnFpDlGUL7YzXv/hmV43foXgPJJQTqO1hr7PFVmeYlEqMV8qNkAjtM5xrzyzKy1kBiXYxeoLFXJ+mYIeW+2jbUUm7UxlDwmXqAwEtCBqPzE2hFXESe6aNkV6ooj/ypiVH5krZMMOEJiQRRLOBvkdF+0GQNlTVUXMc/JlJKgiTScBoBir5gNqsrCyqRk1rkoOfc2CyaKRPDtmTwqyqu9ejda5N9++O+47u9ukHevOPFEfJpjIhYeldptz3a8uSy7Wz9w2fuISbYIc1z3la8NMAdQQqsexBRP6HluMkfOnA0bNqBSq1Alm1+YwKc/8xmhgTPOOBPvIG+nsTDsiSrcEMtVmYCkYoKlXd700nOMrbyINWYJb7q3kzF7y3dtdvhpe2/f1cCwiyPqujzwMYdTk7t27iS7b6vZlSbBvMwgFiYKzBJo185J2axjzBzrivbzATeJNOk8N/HMTf2Ym5/D/Pych0Ci0rn53LjxaYp77PDwPfP0kxJvQTThNkzpog+60A9HsLp8A8W5qI6YBSHGpBBralTUa+D00Xwgcsvydlpe9cx7zNvI+LSS7hzd88jJPekm0jLusrU3EcGh2e6vk0BrkDewQWYm3XltVO4sHUFftWiUGzT6DXnup5Repzx0Z41xeZbvNVNO10fxxLMTuPovvyDMcPgRR+GP/vefy/usPm7rbZndg8ruHiGCfP8HLifmMMcdfeT3P+2XllTRd0wv0Yd5R7h4y1veJviZmJjALyj2VXZnc/6777kbd999N4UK7pGovSsbVxpOvjHfp3fvwV6ak9RkWblb3cZozadIms/JIBTMLu2YxDGNxsKxeQy7Fox1bMlI6sKoaTcJb5PfmbfS8r7tvVN7MUESRTWaNHFeQN6nGSxZuhhPPL6BPFXHYsnipXh+4jlKWyYeq9HREVnOwFtwD1pxEJ588klihrYwxMTmjRght/AcebDYn710yRLsopgKB5EOXbOG0voYGRkVb1VzdFaYZWrvXllkx0A+s/FJ2eO9koJde3Zsped5jI8SwXTN+pwDli3HosWLKkbRfHFGq1uTqGKTp1TEraBGiXEkxpLHzOrH0rhc+VMOYeiY01SIKRKZ/Grrrq35oKSyJ6TwQRdJYhcrKrOqOM8j10sEn4r+U/Fn5JbeRsLtYx//mKQdSIG/P/qTz2N04XKzS8d69/KEtby2Kx0S/PEffw5PPvGElPnI738KZ9soekSuBfyUr3I6a4Rvf+sf5fkzn/kD3HTTV7GaXL4uHzPOtdde5/OfdNJJRTMs6g9v5WUcptJPjdlejxjEHrZgVM4UKYkJ+uRjBWMG4cvthcg8qIet3kFMsgZTs62Bjpx4zCSCnexZgwZJyZ5rVru8WEyJaWQ8RfVaHTuJUFs0ieY9wq1WC4uIqA9cvkS8F/Pzs1iil5K9uVc0DAeoHnvsUUy88DzWHrYWTzzxlPSF691JDLFq9WqKpUyTu/YpQVqfD3cDZB9yvV5Hlwhs7949WEGMwW2XR4Lf7SV4jj36CDz/3CbMk4ZZ/8pX4t777pNPILKRnWWmi1hze5ii0Aa8No1MKTMBd+nw2gi5DhXD+P8SPx4Z/B4VN45+z4qOrCt7hBAf+ECahNeeiTNEqtDR4WnB9tSWeEK6QlgknRBzbMEnPnqlCClOXXEQxRpu/EoJh2Yed8brfh2/ceYZ+PINX8H3br1VXo3b9Vef++ynC3h0Lf4hBQiLV5iNxCnr15+EKy6/HNf/7fXCDJde+m5y+58scZGJic24/fbb/creKy6/AitXHixnnBVMRhizc3TBGJy3jvE6snBhyYvFywVkD1UTfrm11wAaKNjhGs1GH29/w0O45bYTPJM0632cvn4CyxfPw/uXI5OYT+YQ91qaitdgYstWtGf2YqTeEJOox2c9UZmRkRFRwyOkKXjB2WLSApy28ZmNxOldkdPPPfeM1LWQkP3UE6RB5trYuWs7fc6R9piWpSC7dkzioAMOQJuIe9myZZ5ZncG3jLTBIw89JMy3+pBDJXUHReIXL1osMHcJnqeefloWxY2MjuKBhx/GsuXLAnFbKV1wo3EgTJu1beJBUnajqZ1cK088wQyIA3l57sw0F+RzQsy0YTYNWrnvIt7RAkpTvTX3LOPmjqTs9MGdzKjysoaKNYnyFKB8eYM7Xtrulrfz9cD998qNoq6T/1evWoEziUHuu/c+n5+DfLd+9xYMuwYZxOu5qHbzwMe38kET1113nTDJzTdPFEryAsbLiYkuvvhisR7Mlo5ivL1L2mO+Q25xcu9mlIeXMcnq8DXr32NHmqRy/qw09KkP/x6OPXJtsLsjexuR7eyDWvSwfbc5TnJ8lCzYuhlQniPU5UQSbezPxPjD5bQPPj2EvCTddpsPXCXe7EmtfcXLslOBwyz3NmYdr4TtmViVaKBnyCw6bO2hpGWaspy837fOOvZgJXLgLxFa389l/PBT/TV30BovrlPWR+NdtsGs5Ana5JbnhckWkyY7hMyzOId51ras05JuQmvnfircTJmMh4428YiejnZAWveum4vnOuAddpUp3CpnZXQJB/n4EDheJuGOWXX9l91/iZZtsrwzMiGXOCFf5iJyagnjmXHKJ7e4nViJWdnsV07HeFHFZ5473vZv37PmuTXBreZSdhKv7Bzx5PWvINNmPb7//R9g69bNti81mZfo0i5Qd73v8g+hzB5sWvKq6C7FQTI+1YSKjrWInlp1EbabySN28y034/ENj0vknbXIMUcfg3PPPRfj5PHicAB7png1tMRB6kZYy6kmvMxk9y5ZCl9TSlb2Ll68EGr1K97tfZcKe+hjG/7gIx/GsUcdYYWWU/sZCrNP5XDmSCKxmt3uW+DjYzioZhlEhpT3bBNA7ELjwBQvDGO7PicG4TOdOIBlDkkwy855s2FiPVgs6DILAu/Yk33f9sTCfm6lrjUlxH0qni22fI1JaJcRyeBz/IRPI1RJUhwWpQqq3i6YJxjbsm9AVah4VyCOgCNiEuNIMJ9mT4eJgfTk/GFnygQTzK/vQoixeAmvrEy22oT7xnOPPh+RZJfYm/Cd2+TERwppCY6yeSWCI0n9iZQSeOubJfNcJpVFmPYMLjnjN4FryWknZ5a4I1/NQXa5zIMYT2yaS4yqZkw5tufNfpDEwyR4oPlQTsysVTQGVhvr4qhYGEzU3gUKmUE4QDlKsQqOWQihO6T7Gqzst+vYTKAwF4GaWBO/Zsvx/JWXrXA92u7l5zZrbgyEgJn7+9ukkyHohMh8iLSHfA0q2COynN+aCLIQTjYx02CiJ/52mWxmJiorbjZrjskRM/yXh4XdCQIlJcrtA7FnRcmxu24ymph9FDzw0BYEbZaSW142sDhPWbwUQcW8AhegM0jLS+wRumjMo6L+sYcF2gChnNRp8WDMrbpdR+DaFcJIItQp5SfZDqWylsjGfWAPkxCWUPZJ9obknonsedFyWmIip7/0vWmhrJbK7EJEIXxijFqDCbwlp7VzYNSFGM12BYstc5qDX4Yvf5nRZtrGTXSiIs4HYkMtIqMBXPp5zsClBgvFHj471oXKXJ2xFLM0EBS0CW2I4CQzP9NuR6bBfGE/SKKfNcuqo4l4ce4R8joGcrJOx9ljRFjCkLix2yudwWsnE1QzDBJQWMagSU0thcteDFEvZqurO1DNSSgGv0f/Jbmyc1xl2uZa3RIJ9GGa1V4TOAMpDlKbY3p0NMWoZBMPputDHhO6HRw59kAZzapsf5zJoixB6UiruD6JkcjMoawpk9nJuW3LxGnMAQlwG6gctOI5V7KlWUV2N1yQzPbJuDkJZyRJ2HnfUHURQGaJDB/6l9glKswcuRy+zQc48DDwQd7mlPy+Wa+VBmtjgNg5ubBSRiEagOGXHsR7PCRmWqE8wbv35Sr9Cuq4WmUouaaU32bgPJA18ZlbtaRxIMHL+31bQGG+ESaUbiLpmvbMoVQJdOXLyskWKvL9Rz1zR0/qSL2GzkcS3bfjMJKbnynQRhuwaafc/ozcbJDKo9r8+U2ITjcpR/vc5E1HMESHNMSLAlXVYGovoEIed5KHVKG8ZC0er6TgZtMKgVGRh5PwZWUt70G3pyfmBQYxcl62Nifaaz5ft8W966+PKkeCNVFGo6V8kmVmNo2ZSLmJXaTuVBLpAwdyjd1vDtY25p4/ZRKuPSfeNRCTpU+LkBXThdoXpyDCW/nB9TeMhXutDRoGGM1ZFZzaF5PcXMYRwicr5rknrAwHkVTgJec9BOaIPFdxg/5JeSnp1JKuUHV+jZIu1qQ94aJAmGV5UVhEIBNSbcw1ZYhOjLLEspGTjrlbaGhqiJdDe2JRQesVxsVKI6fGndep1K2Bq9Q9aASrWizMXAUpFu9eUwhM45PNd7Nt2BzcwHMNMxmO25Jl2dJOrgYx5ySCcmJOFbrpmpdArWjfrpYD4tzuTHPYuNXGvAQobZDm0HLaS40dD8ouw3HWho4Wd8KYYWbHgwowYCgKI/GLIfmKFBKECkpcgZhYfYeVKmc0B1uwBws2LtTv9+VT5iA6D+TOgzi5cw7HHAkUHGEafi9EQcIicKHXVzE8pWddYTrpitu80J67vSp20i/LPUO6wxULHXfEDQzCo6MB0hEcwwRXucP7vNzSN42S51cIzLNLwTYvQDfQUBwXiWMnHmYdMibQ+7DjdYXJEZeHOE60/NCRg8cytysttjpJ2ox/6Khp1tIh92ut/AYsq9n5Fw3kYMYkXtipItxjANgBGFElouNewcNZ4C5bUBe+DA4iv+HJe588WKn8QI9xzTNyatouIHON5LqOr37rZxRH2I2xESOp2AvCBwSk7FlMGuI1Yq+F/BgMjAGT1keJ0ZxxmVjVTbEStHHI6BxGSOJ0+nlsXRn9lIcOtTMzAIWOe01cQIOfb7nLH+nrBDMwoKlVVSLg7VeUYCvk8f+hIAEHBk9HOXSUQ5UqKZOB1lWUYNktrEw2sFUPcmgTwzgEg6SmguRT5nuQMwEgD7o4Qmo4hLfVHnUiFh20Au2dE9j2xAN4aPOseKV4NyevQjhkvC+Olpk9u7HsmBOxu7YKautjFNMaw56xI80hgCr67RMgxHXKnbAalM2gmZl5TL6wGaNEKscdtYLojoK+qTtxRXnBqCOcKmOxiom6c/c0Zmb7OHTNMtm2wPXyPnd2fzOTJO7Qbspb0zZiHjSDwtxsG1/71v8Du301MUwrmSK/fQPNpINmaxF27pwlJpnHeJ0qbrNkJGnSWENA1BEO3zLAjuW78dplW7G0lWLXXF7oc66d/WtU3POzPOlrIE0ipGQmsOMPZM7tMnntGEX7zampmwBbJKeWi5II6QpRRNiIcttUiEFYHKOwMjbSnrElBK2QQ0e0H0xJWZ2MyLSxWiU21bSOKvfEqCPNFsEVXV5PxRIHg89BR5cZNVzmvLFYA0edjgbMaS/+sa3XHrMOBx5xAta8fD26zz+BuU3346cPPIfJ+S4WjY3hzScfjSVreAUE0dOeSQrPpNgwMo7+L36Il52wAg8tP6QglBL/Y0im/SRJBtplN22H4jY7dk3jF7f9EgcSoSxbejqWLkxlZYR4oCKvnBz+Zgnd8LZR449t2IRNz89gwW++zLifYT2qrDH4dxZlT00mpnvNTz6N49cODg8r/6zAUsrYQpf+Go0O3eakvESNU3BGoUVAzfTIJ9TbSSbPTor98M90tYJZhLDWXp4d8ULZIJK55qjcdH0Zus26HMXSs65QtyCRl5vzLx7xDwWmdPfmZsQPbuYamay1dMPqmM0dyixHbnrPRoAG1uNlFxr4oJYc0+x+RSsmWm2Dd7a8cw1CVucrmcBm2pz/5bfXwvUx4rySnVowqjw8CByszdzOrdmKrdjAILqgaarMCH/WQFmFR2k6ZvBQ0JxKhKBleb6zauUCHNjahtrET3h3AloHL8F714wJVDWii0ajBc1HKVE8ZPmi5RidfgzHdXahvXg3pa2GW1TomMKToNXmYR9/EGD+ikwokYF2mzBHwJUdd/6U31NRLuaT+ZNlrJXu20klLpOauJw2489xr76sxfJLyFVAkJRmBzo1oF8gE2uGCmQyiem1tTCPond8+JqqNc0qXI6KZnMygQsBJtsLIWRDYMohxRrP0+lCbNHLKW6RGs2QZX6AEosBE+8g5mmOozaisOiABK3+DHZOPIPOXBfGyDOnkzsGEReq3UYdb3c1EJnvmZXtjrmMggo/eOMQKRP5PFLZtrz7nZLAfpFGsJg0/3JPhF452pHSdr9+4bgZ346vpaAACgwQMZpAPvDjq7FJFzGlUnZ/SVlPFDVkmNuFuvj7nf2TkHXWopY3JKi4Z+tTFBDUWLz6aDJbmvLrVVk7w9iCcayb/DecfMTheLjx6+g9tgsnrup75jBd0BFTRHB6wTCoxXyfOHhIjJElZpcgM4swhq1LDhi0P0XHxCABVXZHay3LiGRdYN0yCv9SMdEf0xDXuXv3nmIcpDB5zThU1KHnlUhbE9TQnPnl1PYclo6nGCF34NzoCA5YuBPbty0xqyGSFGUdru0Qs698ruMWicEjYOsIqWFmBR3MJkcfuQqmB7t0eYVtL+FVwAnGR0ex6viTMDXxNLZPvCB+eLdPwR22rzxClQmMoYJ2VImorabQ0GFa4JjFMYelEjHnEuuKtXabO886jyW0CqaOdlo6Nt+UI9xIyusAotckPiGe45RRXmQIhQK7IlYbygKcaOXdoLFDwMHriokkZk9azj80RZFwPYq6agmR/eqXP8Oo3otj+vNYcvTrKGjPArRLdY+QOc6rKGiekIzxDycgPv/A0YEfJ6UxQEOxY0KpAl4yN98goGqiBTIJInM6L8FJaqn9Nd1Efp0rUYYK5DdBGg2ZSzMDcZk8y7xU6FPA87kXtokGuYbAKi58kaY5emzWV+n5ccz0F6Pf2YMlYz1k5PvqdVrYvWcEIyNk/zWIGzt1lAyGCLNaTi4caZidaw4p7HnvpSMIRnYpHmIp1BFpYr1WzI18rtWT7S7WrDgchy9dihee3IDu3DzcYnN/dM0AYXhZX4RXo0gUKmgQkbYIDOwOg2R1nMgSF0dZMJoYYQl5WM6uUJyI6xK+Axwl+ggwISaUyKQaLIBiRMnNggbz2doLWspUoH0tflGlJUYeh92bH0M2Oy/aI2vPoNXeLaeAzO18Dpv//Us4/vWXUL9b1gxSXsgE7GvEZk7ZlDLmVqxJYPEa9wuiGdhpwDtQeXKdk+Tnn+1ObRCZr74lfP59S8aEzDNg5j2JLIlR4Yzj3DgCeAnKkmULr6lt3/Cdjx98wttvytp6iRnd3FJNJtN3XlLGHqxev0UBpFHkjUnwz7xP9RaSucWMktpR65LVVDdLWGsZu7lMad1RGYmdmUyrTuY6ZyL1fWGQXH6QRangwNe6pzIKG6dk3NPEn1rrs0HJNqF85/ecnxfZPfncpDrp2JW6Nfqc6vHPrpJnnrlf1cyP0fPiDl662HeffctyNWZP/uFKQ/aEFk6klsJv69WlnAyUJ3H22+WqptyvzluBq2pyNojE10i3ZsqcDQ8dLHpTQPGRDbUanyojV03eMFAEL1XLZjT/5H1G+Ti2i77pi8DXd5gzOGSTgKvPXGW2Nr5kGQsTAiE/4WXNfTOamZyVTLBqLTjUOuVfwePVDZTWF1il3r4yoaDU4IUGROepxQNB+MTj96pm8xFpqbNrF1aP1dBpHiArqdPuJGZ3bkM6tpCk8xh5nab01j2zaqY5CUxPYmZ+FFM7dwqcCQ0ySW7FBMl9qjcaNF/OxADm0/kSndJcOeOfeGdhpGfpc3bXLOamtqt2awxTe3eBj+LqdzoYHR3V3a4p22iMasZrt9tVHXp3wJLlukPtNKm9LVu2kYDtql07dzPtyRhzW1xHShiZ5cWf8/Obznnb2zYJGtevX8/iv2Hv5hFHHNGMPltr167lteyt0nPTpbl8pfRC2fjTPg/UXbqbQ54r35VgiNtolr+/FHjjMg4fpXbi703Oy8/u091VsFT0xcNVwodvy6WX66zqSzx+Jfw3q8YgHseq9HJ+12bcx6r+lGGpyNsq96sibWgZDNJjcxieHfxx/tJY+vu4445jPqifccYZEid0DFKPGSUCrhF9NuJKbEWuoUacFuVvRI3GBOrrceUjIAfyVsBTgKNUx7DPYX2K+9uM6nL9aVQMQKEfL+Wu6kOMl330v1HRXgyXhz8ek4goC7iN0qvq9GXifOU8VXjZR71NVNNVo6q/FThpDMHLAG5KcDUwnJYKOIvKCXPQXXMMwrqtZu96+S4XjL+X3vnvjuEcJ6LILI2Sxqqqr17Oz/eQ9gaYu1RHvapt98l1DIOnCvaqvpRgq1cgfFj/66W26uU2h+Gw3AfXrsN9VR/21c9hY1l691LLDx2LCnz7tDg97nfcv3Jdw3Bdaqde0a+B+t1tGYNvsVST888/P7WJwiz8HGWSArYxl15HYCr/rpzu6nBl3PdSXbWqsigybPm53Ha9qk0Hd6mdgTzlfpTrdGkxTspwRwO8r77UKvpd2V6p35XlyvlK+PYwV5SrO3gr+j8Ae1W7cVoZz0xPES5e9H6peWP8l3Ed01TJIqosH9NxGUcMP+CP/w+/A3DVVVeZcIK9uZDNHGuZNEpLK77X9vXp8pbr5e+ltIH6S5+1cj7+HvfBMb0rFwmBSniH9KtWrmcYnEPuSpzF72K4Sm3tE94yrO4uMUo5vdBenF4xjmUhVwVzJQ4i2qnEXfw+Hs+IWCvrrhrDuEwMb4yT8phW4biEB8cP/sE4i01ml5a4AhHhJa7ROI9Lj9J8esX7QhtxuTgtLhfXOeR7GrX3YrfPF8NbqrcMh9wl+Crrjt6lFfCW+1/uZ9xOGc60ArZKXMfl3F0uVx7juCx77kpjWdlO1RjGfS/DXh7nuL6ImAfqqKq/jNcSMyRlPMS4jctWwOT4wXu/lXVlqvL3EvPEBeM7HoSB9NJzDEihnRiGqrqi9wNwuPJxnnKfYnhKRKNi2GJExXlKMCdVbcRwlOCpKjNQdwVRxrhJynU5GOM6Y5yX0933Un1VjFB4H/UlGVKu8nsZnyUcJOU2KvA+0OfSWCelPlfSAirodkh/iu+djz96gVLlQAUxRg3APgNDmC2uO85TGqyh5eKBLTOSa9vWB1QzO/YB0zDEqQpYh+KiquywMvtoDzEuhuAFFXVX9c/jZB/9RwV8qOjDi41nZZlhbZbophLXpTrwEvFXBdtAeacdh9Ff9In4KhMUUBwQxLftKModi5CAUiOqXAbVgxLXX2ZU4EWIoNSpgU5W1BfDWh6guI4XHYR9EBaGDDRK71BR77D2BsaqNLADgqNURkW4q8JJVX3D+lA5JijhXw8KsMp3ZSYp1VvGMTBIH+X6YnyglLc8Pvuv/df+a/+1/9p/7b/2X/uv/97r/wM7LHoKyFPc/QAAAABJRU5ErkJggg==", "description": "Designed to display single value of the selected attribute or timeseries data. Widget styles are customizable.", "descriptor": { "type": "latest", "sizeX": 5, "sizeY": 1.5, "resources": [], - "templateHtml": "\n", + "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n horizontal: true,\n previewWidth: '420px',\n previewHeight: '130px',\n absoluteHeader: true\n };\n};\n\nself.onDestroy = function() {\n};\n", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueCardWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n horizontal: true,\n previewWidth: '420px',\n previewHeight: '130px',\n embedTitlePanel: true\n };\n};\n\nself.onDestroy = function() {\n};\n", "settingsSchema": "", "dataKeySettingsSchema": "", "settingsDirective": "tb-value-card-widget-settings", "hasBasicMode": true, "basicModeDirective": "tb-value-card-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Horizontal value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Horizontal value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"titleFont\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1.6\"},\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null}}" } } ] diff --git a/ui-ngx/src/app/core/services/dialog.service.ts b/ui-ngx/src/app/core/services/dialog.service.ts index 6ba2687278..c5f54f9cf0 100644 --- a/ui-ngx/src/app/core/services/dialog.service.ts +++ b/ui-ngx/src/app/core/services/dialog.service.ts @@ -21,11 +21,11 @@ import { TranslateService } from '@ngx-translate/core'; import { AuthService } from '@core/auth/auth.service'; import { ColorPickerDialogComponent, - ColorPickerDialogData + ColorPickerDialogData, ColorPickerDialogResult } from '@shared/components/dialog/color-picker-dialog.component'; import { MaterialIconsDialogComponent, - MaterialIconsDialogData + MaterialIconsDialogData, MaterialIconsDialogResult } from '@shared/components/dialog/material-icons-dialog.component'; import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-dialog.component'; import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component'; @@ -96,8 +96,8 @@ export class DialogService { return dialogRef.afterClosed(); } - colorPicker(color: string, colorClearButton = false): Observable { - return this.dialog.open(ColorPickerDialogComponent, + colorPicker(color: string, colorClearButton = false): Observable { + return this.dialog.open(ColorPickerDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], @@ -109,13 +109,14 @@ export class DialogService { }).afterClosed(); } - materialIconPicker(icon: string): Observable { - return this.dialog.open(MaterialIconsDialogComponent, + materialIconPicker(icon: string, iconClearButton = false): Observable { + return this.dialog.open(MaterialIconsDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { - icon + icon, + iconClearButton }, autoFocus: false }).afterClosed(); diff --git a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts index 84134f04a9..b7e21f7b22 100644 --- a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts +++ b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts @@ -33,11 +33,17 @@ import { catchError, map, mergeMap } from 'rxjs/operators'; @NgModule() export abstract class DynamicComponentModule implements OnDestroy { + // eslint-disable-next-line @angular-eslint/contextual-lifecycle ngOnDestroy(): void { } } +interface DynamicComponentData { + componentType: Type; + componentModuleRef: NgModuleRef; +} + interface DynamicComponentModuleData { moduleRef: NgModuleRef; moduleType: Type; @@ -48,22 +54,22 @@ interface DynamicComponentModuleData { }) export class DynamicComponentFactoryService { - private dynamicComponentModulesMap = new Map, DynamicComponentModuleData>(); + private dynamicComponentModulesMap = new Map, DynamicComponentModuleData>(); constructor(private compiler: Compiler, private injector: Injector) { } - public createDynamicComponentFactory( + public createDynamicComponent( componentType: Type, template: string, modules?: Type[], preserveWhitespaces?: boolean, compileAttempt = 1, - styles?: string[]): Observable> { + styles?: string[]): Observable> { return from(import('@angular/compiler')).pipe( mergeMap(() => { - const comp = this.createDynamicComponent(componentType, template, preserveWhitespaces, styles); + const comp = this._createDynamicComponent(componentType, template, preserveWhitespaces, styles); let moduleImports: Type[] = [CommonModule]; if (modules) { moduleImports = [...moduleImports, ...modules]; @@ -82,17 +88,19 @@ export class DynamicComponentFactoryService { this.compiler.clearCacheFor(module.moduleType); throw e; } - const factory = moduleRef.componentFactoryResolver.resolveComponentFactory(comp); - this.dynamicComponentModulesMap.set(factory, { + this.dynamicComponentModulesMap.set(comp, { moduleRef, moduleType: module.moduleType }); - return factory; + return { + componentType: comp, + componentModuleRef: moduleRef + }; }), catchError((error) => { if (compileAttempt === 1) { ɵresetCompiledComponents(); - return this.createDynamicComponentFactory(componentType, template, modules, preserveWhitespaces, ++compileAttempt, styles); + return this.createDynamicComponent(componentType, template, modules, preserveWhitespaces, ++compileAttempt, styles); } else { throw error; } @@ -102,16 +110,16 @@ export class DynamicComponentFactoryService { ); } - public destroyDynamicComponentFactory(factory: ComponentFactory) { - const moduleData = this.dynamicComponentModulesMap.get(factory); + public destroyDynamicComponent(componentType: Type) { + const moduleData = this.dynamicComponentModulesMap.get(componentType); if (moduleData) { moduleData.moduleRef.destroy(); this.compiler.clearCacheFor(moduleData.moduleType); - this.dynamicComponentModulesMap.delete(factory); + this.dynamicComponentModulesMap.delete(componentType); } } - private createDynamicComponent(componentType: Type, template: string, preserveWhitespaces?: boolean, styles?: string[]): Type { + private _createDynamicComponent(componentType: Type, template: string, preserveWhitespaces?: boolean, styles?: string[]): Type { // noinspection AngularMissingOrInvalidDeclarationInModule return Component({ template, diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 9a369cb5aa..2a2f664c3a 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -180,9 +180,7 @@ export function objToBase64(obj: any): string { } export function base64toString(b64Encoded: string): string { - return decodeURIComponent(atob(b64Encoded).split('').map((c) => { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }).join('')); + return decodeURIComponent(atob(b64Encoded).split('').map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('')); } export function objToBase64URI(obj: any): string { @@ -190,9 +188,7 @@ export function objToBase64URI(obj: any): string { } export function base64toObj(b64Encoded: string): any { - const json = decodeURIComponent(atob(b64Encoded).split('').map((c) => { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }).join('')); + const json = decodeURIComponent(atob(b64Encoded).split('').map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('')); return JSON.parse(json); } @@ -355,9 +351,7 @@ const SNAKE_CASE_REGEXP = /[A-Z]/g; export function snakeCase(name: string, separator: string): string { separator = separator || '_'; - return name.replace(SNAKE_CASE_REGEXP, (letter, pos) => { - return (pos ? separator : '') + letter.toLowerCase(); - }); + return name.replace(SNAKE_CASE_REGEXP, (letter, pos) => (pos ? separator : '') + letter.toLowerCase()); } export function getDescendantProp(obj: any, path: string): any { @@ -381,7 +375,7 @@ export function insertVariable(pattern: string, name: string, value: any): strin return result; } -export function createLabelFromDatasource(datasource: Datasource, pattern: string): string { +export const createLabelFromDatasource = (datasource: Datasource, pattern: string): string => { let label = pattern; if (!datasource) { return label; @@ -406,7 +400,9 @@ export function createLabelFromDatasource(datasource: Datasource, pattern: strin match = varsRegex.exec(pattern); } return label; -} +}; + +export const hasDatasourceLabelsVariables = (pattern: string): boolean => varsRegex.test(pattern) !== null; export function formattedDataFormDatasourceData(input: DatasourceData[], dataIndex?: number): FormattedData[] { return _(input).groupBy(el => el.datasource.entityName + el.datasource.entityType) @@ -694,7 +690,7 @@ export function getEntityDetailsPageURL(id: string, entityType: EntityType): str } export function parseHttpErrorMessage(errorResponse: HttpErrorResponse, - translate: TranslateService, responseType?: string): {message: string, timeout: number} { + translate: TranslateService, responseType?: string): {message: string; timeout: number} { let error = null; let errorMessage: string; let timeout = 0; diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html index d58c057b68..b9a1a71c03 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html @@ -196,7 +196,6 @@ flatButton [displayTimewindowValue]="false" [isEdit]="true" - direction="left" tooltipPosition="below" aggregation="true" timezone="true" @@ -205,7 +204,6 @@

diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts index c67a59ac20..6c25585f76 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts @@ -23,7 +23,10 @@ import { WidgetConfigComponentData } from '@home/models/widget-component.models' import { DataKey, Datasource, WidgetConfig } from '@shared/models/widget.models'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { isUndefined } from '@core/utils'; -import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component'; +import { + getTimewindowConfig, + setTimewindowConfig +} from '@home/components/widget/config/timewindow-config-panel.component'; @Component({ selector: 'tb-alarms-table-basic-config', @@ -74,9 +77,7 @@ export class AlarmsTableBasicConfigComponent extends BasicWidgetConfigComponent } protected prepareOutputConfig(config: any): WidgetConfigComponentData { - this.widgetConfig.config.useDashboardTimewindow = config.timewindowConfig.useDashboardTimewindow; - this.widgetConfig.config.displayTimewindow = config.timewindowConfig.displayTimewindow; - this.widgetConfig.config.timewindow = config.timewindowConfig.timewindow; + setTimewindowConfig(this.widgetConfig.config, config.timewindowConfig); this.widgetConfig.config.alarmFilterConfig = config.alarmFilterConfig; this.widgetConfig.config.alarmSource = config.datasources[0]; this.setColumns(config.columns, this.widgetConfig.config.alarmSource); diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html index b4c2dbd616..ae0ca13dbe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html @@ -64,6 +64,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.ts index f407293064..228879e2ef 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.ts @@ -29,7 +29,10 @@ import { import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { isUndefined } from '@core/utils'; -import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component'; +import { + getTimewindowConfig, + setTimewindowConfig +} from '@home/components/widget/config/timewindow-config-panel.component'; @Component({ selector: 'tb-entities-table-basic-config', @@ -93,9 +96,7 @@ export class EntitiesTableBasicConfigComponent extends BasicWidgetConfigComponen } protected prepareOutputConfig(config: any): WidgetConfigComponentData { - this.widgetConfig.config.useDashboardTimewindow = config.timewindowConfig.useDashboardTimewindow; - this.widgetConfig.config.displayTimewindow = config.timewindowConfig.displayTimewindow; - this.widgetConfig.config.timewindow = config.timewindowConfig.timewindow; + setTimewindowConfig(this.widgetConfig.config, config.timewindowConfig); this.widgetConfig.config.datasources = config.datasources; this.setColumns(config.columns, this.widgetConfig.config.datasources); this.widgetConfig.config.actions = config.actions; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts index 283e39362a..9b2a6a764b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts @@ -27,7 +27,10 @@ import { } from '@shared/models/widget.models'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; -import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component'; +import { + getTimewindowConfig, + setTimewindowConfig +} from '@home/components/widget/config/timewindow-config-panel.component'; import { isUndefined } from '@core/utils'; import { getLabel, setLabel } from '@shared/models/widget-settings.models'; @@ -80,9 +83,7 @@ export class SimpleCardBasicConfigComponent extends BasicWidgetConfigComponent { } protected prepareOutputConfig(config: any): WidgetConfigComponentData { - this.widgetConfig.config.useDashboardTimewindow = config.timewindowConfig.useDashboardTimewindow; - this.widgetConfig.config.displayTimewindow = config.timewindowConfig.displayTimewindow; - this.widgetConfig.config.timewindow = config.timewindowConfig.timewindow; + setTimewindowConfig(this.widgetConfig.config, config.timewindowConfig); this.widgetConfig.config.datasources = config.datasources; setLabel(config.label, this.widgetConfig.config.datasources); this.widgetConfig.config.actions = config.actions; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html index 5952c206b6..15c903736a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html @@ -64,6 +64,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts index a8c4206fa7..dc2a41b816 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts @@ -24,7 +24,10 @@ import { DataKey, Datasource, WidgetConfig } from '@shared/models/widget.models' import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { deepClone, isUndefined } from '@core/utils'; -import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component'; +import { + getTimewindowConfig, + setTimewindowConfig +} from '@home/components/widget/config/timewindow-config-panel.component'; @Component({ selector: 'tb-timeseries-table-basic-config', @@ -79,9 +82,7 @@ export class TimeseriesTableBasicConfigComponent extends BasicWidgetConfigCompon } protected prepareOutputConfig(config: any): WidgetConfigComponentData { - this.widgetConfig.config.useDashboardTimewindow = config.timewindowConfig.useDashboardTimewindow; - this.widgetConfig.config.displayTimewindow = config.timewindowConfig.displayTimewindow; - this.widgetConfig.config.timewindow = config.timewindowConfig.timewindow; + setTimewindowConfig(this.widgetConfig.config, config.timewindowConfig); this.widgetConfig.config.datasources = config.datasources; this.setColumns(config.columns, this.widgetConfig.config.datasources); this.widgetConfig.config.actions = config.actions; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html index 356612564f..a036ab2b1c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html @@ -65,6 +65,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts index b535aaa7f8..dacb2dfa11 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts @@ -27,7 +27,10 @@ import { } from '@shared/models/widget.models'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; -import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component'; +import { + getTimewindowConfig, + setTimewindowConfig +} from '@home/components/widget/config/timewindow-config-panel.component'; import { formatValue, isDefinedAndNotNull, isUndefined } from '@core/utils'; import { DateFormatProcessor, @@ -145,9 +148,7 @@ export class ValueCardBasicConfigComponent extends BasicWidgetConfigComponent { } protected prepareOutputConfig(config: any): WidgetConfigComponentData { - this.widgetConfig.config.useDashboardTimewindow = config.timewindowConfig.useDashboardTimewindow; - this.widgetConfig.config.displayTimewindow = config.timewindowConfig.displayTimewindow; - this.widgetConfig.config.timewindow = config.timewindowConfig.timewindow; + setTimewindowConfig(this.widgetConfig.config, config.timewindowConfig); this.widgetConfig.config.datasources = config.datasources; this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html index 0d607c230d..6bd31f41eb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html @@ -64,6 +64,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts index c77d0ecb76..050d362868 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts @@ -24,7 +24,10 @@ import { DataKey, Datasource, WidgetConfig } from '@shared/models/widget.models' import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { isUndefined } from '@core/utils'; -import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component'; +import { + getTimewindowConfig, + setTimewindowConfig +} from '@home/components/widget/config/timewindow-config-panel.component'; @Component({ selector: 'tb-flot-basic-config', @@ -83,9 +86,7 @@ export class FlotBasicConfigComponent extends BasicWidgetConfigComponent { } protected prepareOutputConfig(config: any): WidgetConfigComponentData { - this.widgetConfig.config.useDashboardTimewindow = config.timewindowConfig.useDashboardTimewindow; - this.widgetConfig.config.displayTimewindow = config.timewindowConfig.displayTimewindow; - this.widgetConfig.config.timewindow = config.timewindowConfig.timewindow; + setTimewindowConfig(this.widgetConfig.config, config.timewindowConfig); this.widgetConfig.config.datasources = config.datasources; this.setSeries(config.series, this.widgetConfig.config.datasources); this.widgetConfig.config.actions = config.actions; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/timewindow-config-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-config-panel.component.html index 045d41ee03..d144cb3db6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/timewindow-config-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-config-panel.component.html @@ -27,14 +27,19 @@ {{ 'widget-config.display-timewindow' | translate }} - +
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/timewindow-config-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-config-panel.component.ts index 410896dd74..93fc304b7a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/timewindow-config-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-config-panel.component.ts @@ -22,11 +22,13 @@ import { Timewindow } from '@shared/models/time/time.models'; import { TranslateService } from '@ngx-translate/core'; import { coerceBoolean } from '@shared/decorators/coercion'; import { isDefined } from '@core/utils'; +import { TimewindowStyle } from '@shared/models/widget-settings.models'; export interface TimewindowConfigData { useDashboardTimewindow: boolean; displayTimewindow: boolean; timewindow: Timewindow; + timewindowStyle: TimewindowStyle; } export const getTimewindowConfig = (config: WidgetConfig): TimewindowConfigData => ({ @@ -34,9 +36,17 @@ export const getTimewindowConfig = (config: WidgetConfig): TimewindowConfigData config.useDashboardTimewindow : true, displayTimewindow: isDefined(config.displayTimewindow) ? config.displayTimewindow : true, - timewindow: config.timewindow + timewindow: config.timewindow, + timewindowStyle: config.timewindowStyle }); +export const setTimewindowConfig = (config: WidgetConfig, data: TimewindowConfigData): void => { + config.useDashboardTimewindow = data.useDashboardTimewindow; + config.displayTimewindow = data.displayTimewindow; + config.timewindow = data.timewindow; + config.timewindowStyle = data.timewindowStyle; +}; + @Component({ selector: 'tb-timewindow-config-panel', templateUrl: './timewindow-config-panel.component.html', @@ -78,7 +88,8 @@ export class TimewindowConfigPanelComponent implements ControlValueAccessor, OnI this.timewindowConfig = this.fb.group({ useDashboardTimewindow: [null, []], displayTimewindow: [null, []], - timewindow: [null, []] + timewindow: [null, []], + timewindowStyle: [null, []] }); this.timewindowConfig.valueChanges.subscribe( (val) => this.propagateChange(val) @@ -86,6 +97,9 @@ export class TimewindowConfigPanelComponent implements ControlValueAccessor, OnI this.timewindowConfig.get('useDashboardTimewindow').valueChanges.subscribe(() => { this.updateTimewindowConfigEnabledState(); }); + this.timewindowConfig.get('displayTimewindow').valueChanges.subscribe(() => { + this.updateTimewindowConfigEnabledState(); + }); } writeValue(data?: TimewindowConfigData): void { @@ -112,12 +126,19 @@ export class TimewindowConfigPanelComponent implements ControlValueAccessor, OnI private updateTimewindowConfigEnabledState() { const useDashboardTimewindow: boolean = this.timewindowConfig.get('useDashboardTimewindow').value; + const displayTimewindow: boolean = this.timewindowConfig.get('displayTimewindow').value; if (useDashboardTimewindow) { this.timewindowConfig.get('displayTimewindow').disable({emitEvent: false}); this.timewindowConfig.get('timewindow').disable({emitEvent: false}); + this.timewindowConfig.get('timewindowStyle').disable({emitEvent: false}); } else { this.timewindowConfig.get('displayTimewindow').enable({emitEvent: false}); this.timewindowConfig.get('timewindow').enable({emitEvent: false}); + if (displayTimewindow) { + this.timewindowConfig.get('timewindowStyle').enable({emitEvent: false}); + } else { + this.timewindowConfig.get('timewindowStyle').disable({emitEvent: false}); + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style-panel.component.html new file mode 100644 index 0000000000..5ba8df3012 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style-panel.component.html @@ -0,0 +1,97 @@ + +
+
timewindow.style
+
+ + {{ 'timewindow.icon' | translate }} + +
+ + + + + +
+
+
+
timewindow.icon-position
+ + + + {{ 'timewindow.icon-position-left' | translate }} + + + {{ 'timewindow.icon-position-right' | translate }} + + + +
+
+
timewindow.font
+ + +
+
+
timewindow.color
+ + +
+ +
+
timewindow.preview
+ + +
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style-panel.component.scss new file mode 100644 index 0000000000..d3c2dfb4fb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style-panel.component.scss @@ -0,0 +1,54 @@ +/** + * 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. + */ + +@import '../../../../../../scss/constants'; + +.tb-timewindow-style-panel { + width: 100%; + display: flex; + flex-direction: column; + gap: 16px; + @media #{$mat-gt-xs} { + min-width: 320px; + } + .tb-timewindow-style-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-form-row { + .fixed-title-width { + min-width: 120px; + } + &.timewindow-preview { + align-items: flex-start; + tb-timewindow { + font-size: 14px; + opacity: .85; + } + } + } + .tb-timewindow-style-panel-buttons { + height: 60px; + display: flex; + flex-direction: row; + gap: 16px; + justify-content: flex-end; + align-items: flex-end; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style-panel.component.ts new file mode 100644 index 0000000000..e0e4ee5615 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style-panel.component.ts @@ -0,0 +1,108 @@ +/// +/// 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. +/// + +import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { defaultTimewindowStyle, TimewindowStyle } from '@shared/models/widget-settings.models'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Timewindow } from '@shared/models/time/time.models'; +import { deepClone } from '@core/utils'; + +@Component({ + selector: 'tb-timewindow-style-panel', + templateUrl: './timewindow-style-panel.component.html', + providers: [], + styleUrls: ['./timewindow-style-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class TimewindowStylePanelComponent extends PageComponent implements OnInit { + + @Input() + timewindowStyle: TimewindowStyle; + + @Input() + previewValue: Timewindow; + + @Input() + popover: TbPopoverComponent; + + @Output() + timewindowStyleApplied = new EventEmitter(); + + timewindowStyleFormGroup: UntypedFormGroup; + + previewTimewindowStyle: TimewindowStyle; + + constructor(private fb: UntypedFormBuilder, + protected store: Store) { + super(store); + } + + ngOnInit(): void { + const computedTimewindowStyle = {...defaultTimewindowStyle, ...(this.timewindowStyle || {})}; + this.timewindowStyleFormGroup = this.fb.group( + { + showIcon: [computedTimewindowStyle.showIcon, []], + iconSize: [computedTimewindowStyle.iconSize, []], + icon: [computedTimewindowStyle.icon, []], + iconPosition: [computedTimewindowStyle.iconPosition, []], + font: [computedTimewindowStyle.font, []], + color: [computedTimewindowStyle.color, []] + } + ); + this.updatePreviewStyle(this.timewindowStyle); + this.updateTimewindowStyleEnabledState(); + this.timewindowStyleFormGroup.valueChanges.subscribe((timewindowStyle: TimewindowStyle) => { + if (this.timewindowStyleFormGroup.valid) { + this.updatePreviewStyle(timewindowStyle); + setTimeout(() => {this.popover?.updatePosition();}, 0); + } + }); + this.timewindowStyleFormGroup.get('showIcon').valueChanges.subscribe(() => { + this.updateTimewindowStyleEnabledState(); + }); + } + + cancel() { + this.popover?.hide(); + } + + applyTimewindowStyle() { + const timewindowStyle = this.timewindowStyleFormGroup.getRawValue(); + this.timewindowStyleApplied.emit(timewindowStyle); + } + + private updateTimewindowStyleEnabledState() { + const showIcon: boolean = this.timewindowStyleFormGroup.get('showIcon').value; + if (showIcon) { + this.timewindowStyleFormGroup.get('iconSize').enable({emitEvent: false}); + this.timewindowStyleFormGroup.get('icon').enable({emitEvent: false}); + this.timewindowStyleFormGroup.get('iconPosition').enable({emitEvent: false}); + } else { + this.timewindowStyleFormGroup.get('iconSize').disable({emitEvent: false}); + this.timewindowStyleFormGroup.get('icon').disable({emitEvent: false}); + this.timewindowStyleFormGroup.get('iconPosition').disable({emitEvent: false}); + } + } + + private updatePreviewStyle(timewindowStyle: TimewindowStyle) { + this.previewTimewindowStyle = deepClone(timewindowStyle); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style.component.html b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style.component.html new file mode 100644 index 0000000000..2bd981fffc --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style.component.html @@ -0,0 +1,25 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style.component.ts new file mode 100644 index 0000000000..ad6d3e1b85 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style.component.ts @@ -0,0 +1,97 @@ +/// +/// 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. +/// + +import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { TimewindowStyle } from '@shared/models/widget-settings.models'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { Timewindow } from '@shared/models/time/time.models'; +import { TimewindowStylePanelComponent } from '@home/components/widget/config/timewindow-style-panel.component'; + +@Component({ + selector: 'tb-timewindow-style', + templateUrl: './timewindow-style.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimewindowStyleComponent), + multi: true + } + ] +}) +export class TimewindowStyleComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled: boolean; + + @Input() + previewValue: Timewindow; + + private modelValue: TimewindowStyle; + + private propagateChange = null; + + constructor(private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef) {} + + ngOnInit(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: TimewindowStyle): void { + this.modelValue = value; + } + + openTimewindowStylePopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + timewindowStyle: this.modelValue, + previewValue: this.previewValue + }; + const timewindowStylePanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, TimewindowStylePanelComponent, 'left', true, null, + ctx, + {}, + {}, {}, true); + timewindowStylePanelPopover.tbComponentRef.instance.popover = timewindowStylePanelPopover; + timewindowStylePanelPopover.tbComponentRef.instance.timewindowStyleApplied.subscribe((timewindowStyle) => { + timewindowStylePanelPopover.hide(); + this.modelValue = timewindowStyle; + this.propagateChange(this.modelValue); + }); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts index 392a829955..ea0d27dcf6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts @@ -30,6 +30,8 @@ import { WidgetSettingsModule } from '@home/components/widget/lib/settings/widge import { WidgetSettingsComponent } from '@home/components/widget/config/widget-settings.component'; import { TimewindowConfigPanelComponent } from '@home/components/widget/config/timewindow-config-panel.component'; import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings/common/widget-settings-common.module'; +import { TimewindowStyleComponent } from '@home/components/widget/config/timewindow-style.component'; +import { TimewindowStylePanelComponent } from '@home/components/widget/config/timewindow-style-panel.component'; @NgModule({ declarations: @@ -43,6 +45,8 @@ import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings DatasourcesComponent, EntityAliasSelectComponent, FilterSelectComponent, + TimewindowStyleComponent, + TimewindowStylePanelComponent, TimewindowConfigPanelComponent, WidgetSettingsComponent ], @@ -62,6 +66,8 @@ import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings DatasourcesComponent, EntityAliasSelectComponent, FilterSelectComponent, + TimewindowStyleComponent, + TimewindowStylePanelComponent, TimewindowConfigPanelComponent, WidgetSettingsComponent, WidgetSettingsCommonModule diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts index dc3e9c7ac2..3c20317b13 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts @@ -20,8 +20,8 @@ import { ComponentFactory, ComponentRef, HostBinding, Inject, - Injector, - OnDestroy, + Injector, NgModuleRef, + OnDestroy, Type, ViewContainerRef } from '@angular/core'; import { DialogComponent } from '@shared/components/dialog.component'; @@ -35,11 +35,13 @@ import { } from '@home/components/widget/dialog/custom-dialog.component'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; +import { DynamicComponentModule } from '@core/services/dynamic-component-factory.service'; export interface CustomDialogContainerData { controller: (instance: CustomDialogComponent) => void; data?: any; - customComponentFactory: ComponentFactory; + customComponentType: Type; + customComponentModuleRef: NgModuleRef; } @Component({ @@ -77,7 +79,8 @@ export class CustomDialogContainerComponent extends DialogComponent> + private customModules: Array>; constructor( private translate: TranslateService, @@ -56,12 +56,13 @@ export class CustomDialogService { if (Array.isArray(this.customModules)) { modules.push(...this.customModules); } - return this.dynamicComponentFactoryService.createDynamicComponentFactory( + return this.dynamicComponentFactoryService.createDynamicComponent( class CustomDialogComponentInstance extends CustomDialogComponent {}, template, modules).pipe( - mergeMap((factory) => { + mergeMap((componentData) => { const dialogData: CustomDialogContainerData = { controller, - customComponentFactory: factory, + customComponentType: componentData.componentType, + customComponentModuleRef: componentData.componentModuleRef, data }; let dialogConfig: MatDialogConfig = { @@ -76,7 +77,7 @@ export class CustomDialogService { CustomDialogContainerComponent, dialogConfig).afterClosed().pipe( tap(() => { - this.dynamicComponentFactoryService.destroyDynamicComponentFactory(factory); + this.dynamicComponentFactoryService.destroyDynamicComponent(componentData.componentType); }) ); } diff --git a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts index 22fac750b5..feeb1fe802 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts @@ -15,7 +15,7 @@ /// import { PageComponent } from '@shared/components/page.component'; -import { Directive, Injector, OnDestroy, OnInit } from '@angular/core'; +import { Directive, Injector, OnDestroy, OnInit, TemplateRef } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { IDynamicWidgetComponent, WidgetContext } from '@home/models/widget-component.models'; @@ -67,7 +67,8 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid @TbInject(UntypedFormBuilder) public fb: UntypedFormBuilder, @TbInject(Injector) public readonly $injector: Injector, @TbInject('widgetContext') public readonly ctx: WidgetContext, - @TbInject('errorMessages') public readonly errorMessages: string[]) { + @TbInject('errorMessages') public readonly errorMessages: string[], + @TbInject('widgetTitlePanel') public readonly widgetTitlePanel: TemplateRef) { super(store); this.ctx.$injector = $injector; this.ctx.deviceService = $injector.get(DeviceService); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index f799ed43ff..234443855f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -37,16 +37,7 @@ import { DataKey, WidgetActionDescriptor, WidgetConfig } from '@shared/models/wi import { IWidgetSubscription } from '@core/api/widget-api.models'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; -import { - createLabelFromDatasource, - deepClone, - hashCode, - isDefined, - isDefinedAndNotNull, - isNumber, - isObject, - isUndefined -} from '@core/utils'; +import { deepClone, hashCode, isDefined, isDefinedAndNotNull, isNumber, isObject, isUndefined } from '@core/utils'; import cssjs from '@core/css/css'; import { sortItems } from '@shared/models/page/page-link'; import { Direction } from '@shared/models/page/sort-order'; @@ -194,8 +185,6 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, private subscription: IWidgetSubscription; private widgetResize$: ResizeObserver; - private alarmsTitlePattern: string; - private displayActivity = false; private displayDetails = true; public allowAcknowledgment = true; @@ -322,7 +311,6 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, } public onDataUpdated() { - this.updateTitle(true); this.alarmsDatasource.updateAlarms(); this.clearCache(); this.ctx.detectChanges(); @@ -342,13 +330,11 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, this.allowAssign = isDefined(this.settings.allowAssign) ? this.settings.allowAssign : true; if (this.settings.alarmsTitle && this.settings.alarmsTitle.length) { - this.alarmsTitlePattern = this.utils.customTranslation(this.settings.alarmsTitle, this.settings.alarmsTitle); + this.ctx.widgetTitle = this.settings.alarmsTitle; } else { - this.alarmsTitlePattern = this.translate.instant('alarm.alarms'); + this.ctx.widgetTitle = this.translate.instant('alarm.alarms'); } - this.updateTitle(false); - this.enableSelection = isDefined(this.settings.enableSelection) ? this.settings.enableSelection : true; if (!this.allowAcknowledgment && !this.allowClear) { this.enableSelection = false; @@ -394,16 +380,6 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, $(this.elementRef.nativeElement).addClass(namespace); } - private updateTitle(updateWidgetParams = false) { - const newTitle = createLabelFromDatasource(this.subscription.alarmSource, this.alarmsTitlePattern); - if (this.ctx.widgetTitle !== newTitle) { - this.ctx.widgetTitle = newTitle; - if (updateWidgetParams) { - this.ctx.updateWidgetParams(); - } - } - } - private updateAlarmSource() { if (this.enableSelection) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.html index 8c79c0e1e7..fee7dc5259 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.html @@ -17,6 +17,9 @@ -->
+
+ +
@@ -60,7 +63,7 @@ {{ icon }} -
{{ label }}
+
{{ label$ | async }}
{{ dateFormat.formatted }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.scss index d0cf0ea0b7..05bbbb0bf0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.scss @@ -44,6 +44,13 @@ bottom: 12px; right: 12px; } + > div.tb-value-card-title-panel { + position: absolute; + top: 12px; + left: 12px; + right: 12px; + z-index: 2; + } .tb-value-card-icon-row { display: flex; flex-direction: row; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts index ac2cb62fee..5505a1e8dd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnInit, TemplateRef } from '@angular/core'; import { WidgetContext } from '@home/models/widget-component.models'; import { formatValue, isDefinedAndNotNull } from '@core/utils'; import { DatePipe } from '@angular/common'; @@ -31,6 +31,7 @@ import { } from '@shared/models/widget-settings.models'; import { valueCardDefaultSettings, ValueCardLayout, ValueCardWidgetSettings } from './value-card-widget.models'; import { WidgetComponent } from '@home/components/widget/widget.component'; +import { Observable } from 'rxjs'; @Component({ selector: 'tb-value-card-widget', @@ -46,6 +47,9 @@ export class ValueCardWidgetComponent implements OnInit { @Input() ctx: WidgetContext; + @Input() + widgetTitlePanel: TemplateRef; + layout: ValueCardLayout; showIcon = true; icon = ''; @@ -53,7 +57,7 @@ export class ValueCardWidgetComponent implements OnInit { iconColor: ColorProcessor; showLabel = true; - label = ''; + label$: Observable; labelStyle: ComponentStyle = {}; labelColor: ColorProcessor; @@ -102,15 +106,16 @@ export class ValueCardWidgetComponent implements OnInit { this.iconColor = ColorProcessor.fromSettings(this.settings.iconColor); this.showLabel = this.settings.showLabel; - this.label = getLabel(this.ctx.datasources); - this.labelStyle = textStyle(this.settings.labelFont, '1.5', '0.25px'); + const label = getLabel(this.ctx.datasources); + this.label$ = this.ctx.registerLabelPattern('valueCardLabel', label); + this.labelStyle = textStyle(this.settings.labelFont, '0.25px'); this.labelColor = ColorProcessor.fromSettings(this.settings.labelColor); - this.valueStyle = textStyle(this.settings.valueFont, '100%', '0.13px'); + this.valueStyle = textStyle(this.settings.valueFont, '0.13px'); this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor); this.showDate = this.settings.showDate; this.dateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.dateFormat); - this.dateStyle = textStyle(this.settings.dateFont, '1.33', '0.25px'); + this.dateStyle = textStyle(this.settings.dateFont, '0.25px'); this.dateColor = ColorProcessor.fromSettings(this.settings.dateColor); this.backgroundStyle = backgroundStyle(this.settings.background); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts index 549ba4abcd..aa442bd199 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts @@ -89,7 +89,8 @@ export const valueCardDefaultSettings = (horizontal: boolean): ValueCardWidgetSe size: 16, sizeUnit: 'px', style: 'normal', - weight: '500' + weight: '500', + lineHeight: '1.5' }, labelColor: constantColor('rgba(0, 0, 0, 0.87)'), showIcon: true, @@ -102,7 +103,8 @@ export const valueCardDefaultSettings = (horizontal: boolean): ValueCardWidgetSe size: 52, sizeUnit: 'px', style: 'normal', - weight: '500' + weight: '500', + lineHeight: '100%' }, valueColor: constantColor('rgba(0, 0, 0, 0.87)'), showDate: true, @@ -112,7 +114,8 @@ export const valueCardDefaultSettings = (horizontal: boolean): ValueCardWidgetSe size: 12, sizeUnit: 'px', style: 'normal', - weight: '500' + weight: '500', + lineHeight: '1.33' }, dateColor: constantColor('rgba(0, 0, 0, 0.38)'), background: { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts index 07b05db0f2..ecb10f3ade 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts @@ -42,15 +42,7 @@ import { import { IWidgetSubscription } from '@core/api/widget-api.models'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; -import { - createLabelFromDatasource, - deepClone, - hashCode, - isDefined, - isNumber, - isObject, - isUndefined -} from '@core/utils'; +import { deepClone, hashCode, isDefined, isNumber, isObject, isUndefined } from '@core/utils'; import cssjs from '@core/css/css'; import { CollectionViewer, DataSource } from '@angular/cdk/collections'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; @@ -164,8 +156,6 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni private subscription: IWidgetSubscription; private widgetResize$: ResizeObserver; - private entitiesTitlePattern: string; - private defaultPageSize = 10; private defaultSortOrder = 'entityName'; @@ -266,7 +256,6 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni } public onDataUpdated() { - this.updateTitle(true); this.entityDatasource.dataUpdated(); this.clearCache(); this.ctx.detectChanges(); @@ -281,16 +270,15 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni this.setCellButtonAction = !!this.ctx.actionsApi.getActionDescriptors('actionCellButton').length; - this.hasRowAction = !!this.ctx.actionsApi.getActionDescriptors('rowClick').length || !!this.ctx.actionsApi.getActionDescriptors('rowDoubleClick').length; + this.hasRowAction = !!this.ctx.actionsApi.getActionDescriptors('rowClick').length || + !!this.ctx.actionsApi.getActionDescriptors('rowDoubleClick').length; if (this.settings.entitiesTitle && this.settings.entitiesTitle.length) { - this.entitiesTitlePattern = this.utils.customTranslation(this.settings.entitiesTitle, this.settings.entitiesTitle); + this.ctx.widgetTitle = this.settings.entitiesTitle; } else { - this.entitiesTitlePattern = this.translate.instant('entity.entities'); + this.ctx.widgetTitle = this.translate.instant('entity.entities'); } - this.updateTitle(false); - this.searchAction.show = isDefined(this.settings.enableSearch) ? this.settings.enableSearch : true; this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true; this.enableStickyHeader = isDefined(this.settings.enableStickyHeader) ? this.settings.enableStickyHeader : true; @@ -319,16 +307,6 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni $(this.elementRef.nativeElement).addClass(namespace); } - private updateTitle(updateWidgetParams = false) { - const newTitle = createLabelFromDatasource(this.subscription.datasources[0], this.entitiesTitlePattern); - if (this.ctx.widgetTitle !== newTitle) { - this.ctx.widgetTitle = newTitle; - if (updateWidgetParams) { - this.ctx.updateWidgetParams(); - } - } - } - private updateDatasources() { const displayEntityName = isDefined(this.settings.displayEntityName) ? this.settings.displayEntityName : true; @@ -498,14 +476,12 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni overlayRef.dispose(); }); - const columns: DisplayColumn[] = this.columns.map(column => { - return { + const columns: DisplayColumn[] = this.columns.map(column => ({ title: column.title, def: column.def, display: this.displayedColumns.indexOf(column.def) > -1, selectable: this.columnSelectionAvailability[column.def] - }; - }); + })); const providers: StaticProvider[] = [ { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.ts index 20cce79457..2e9a9fb5f9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.ts @@ -94,12 +94,8 @@ export class JsonInputWidgetComponent extends PageComponent implements OnInit { private initializeConfig() { if (this.settings.widgetTitle && this.settings.widgetTitle.length) { - const title = createLabelFromDatasource(this.datasource, this.settings.widgetTitle); - this.ctx.widgetTitle = this.utils.customTranslation(title, title); - } else { - this.ctx.widgetTitle = this.ctx.widgetConfig.title; + this.ctx.widgetTitle = this.settings.widgetTitle; } - if (this.settings.labelValue && this.settings.labelValue.length) { const label = createLabelFromDatasource(this.datasource, this.settings.labelValue); this.labelValue = this.utils.customTranslation(label, label); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index 6f29a495cf..f667c785c1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -202,10 +202,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni private initializeConfig() { if (this.settings.widgetTitle && this.settings.widgetTitle.length) { - const titlePatternText = this.utils.customTranslation(this.settings.widgetTitle, this.settings.widgetTitle); - this.ctx.widgetTitle = createLabelFromDatasource(this.datasources[0], titlePatternText); - } else { - this.ctx.widgetTitle = this.ctx.widgetConfig.title; + this.ctx.widgetTitle = this.settings.widgetTitle; } this.settings.groupTitle = this.settings.groupTitle || '${entityName}'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html index 71a9b60118..73ee11f393 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html @@ -50,6 +50,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html index 36d488d766..9fe3273f74 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.html @@ -72,6 +72,12 @@
+
+
widgets.widget-font.line-height
+ + + +
widgets.widget-font.preview
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts index 2f86394f44..a0e77ef380 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts @@ -101,7 +101,8 @@ export class FontSettingsPanelComponent extends PageComponent implements OnInit sizeUnit: [(this.font?.sizeUnit || 'px'), []], family: [this.font?.family, []], weight: [this.font?.weight, []], - style: [this.font?.style, []] + style: [this.font?.style, []], + lineHeight: [this.font?.lineHeight, []] } ); this.updatePreviewStyle(this.font); @@ -146,7 +147,7 @@ export class FontSettingsPanelComponent extends PageComponent implements OnInit } private updatePreviewStyle(font: Font) { - this.previewStyle = {...(this.initialPreviewStyle || {}), ...textStyle(font, '1')}; + this.previewStyle = {...(this.initialPreviewStyle || {}), ...textStyle(font)}; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index d3dbb9853c..1b73ccbe4a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -227,7 +227,7 @@ export class WidgetComponentService { } public clearWidgetInfo(widgetInfo: WidgetInfo, bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): void { - this.dynamicComponentFactoryService.destroyDynamicComponentFactory(widgetInfo.componentFactory); + this.dynamicComponentFactoryService.destroyDynamicComponent(widgetInfo.componentType); this.widgetService.deleteWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem); } @@ -362,13 +362,14 @@ export class WidgetComponentService { return of(resolvedModules); } else { this.registerWidgetSettingsForms(widgetInfo, resolvedModules.factories); - return this.dynamicComponentFactoryService.createDynamicComponentFactory( + return this.dynamicComponentFactoryService.createDynamicComponent( class DynamicWidgetComponentInstance extends DynamicWidgetComponent {}, widgetInfo.templateHtml, resolvedModules.modules ).pipe( - map((factory) => { - widgetInfo.componentFactory = factory; + map((componentData) => { + widgetInfo.componentType = componentData.componentType; + widgetInfo.componentModuleRef = componentData.componentModuleRef; return null; }), catchError(e => { @@ -546,8 +547,8 @@ export class WidgetComponentService { if (isUndefined(result.typeParameters.previewHeight)) { result.typeParameters.previewHeight = '70%'; } - if (isUndefined(result.typeParameters.absoluteHeader)) { - result.typeParameters.absoluteHeader = false; + if (isUndefined(result.typeParameters.embedTitlePanel)) { + result.typeParameters.embedTitlePanel = false; } if (isFunction(widgetTypeInstance.actionSources)) { result.actionSources = widgetTypeInstance.actionSources(); diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html index 5f54970859..23440260d4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html @@ -70,6 +70,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index da35390ffd..2bdf662469 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -369,7 +369,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe this.dataSettings.addControl('timewindowConfig', this.fb.control({ useDashboardTimewindow: true, displayTimewindow: true, - timewindow: null + timewindow: null, + timewindowStyle: null })); if (this.widgetType === widgetType.alarm) { this.dataSettings.addControl('alarmFilterConfig', this.fb.control(null)); @@ -517,7 +518,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe useDashboardTimewindow, displayTimewindow: isDefined(config.displayTimewindow) ? config.displayTimewindow : true, - timewindow: config.timewindow + timewindow: config.timewindow, + timewindowStyle: config.timewindowStyle }, {emitEvent: false}); } if (this.modelValue.isDataEnabled) { diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html index 43b54884ec..1e3fd33171 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html @@ -32,34 +32,13 @@ (click)="onClicked($event)" (contextmenu)="onContextMenu($event)">
-
- - {{widget.titleIcon}} - {{widget.customTranslatedTitle}} - - - -
+ class="tb-widget-header"> + + +
+ +
+
+ {{widget.titleIcon}} +
+ {{widget.title$ | async}} +
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss index 52caeb2a5c..a61dc463db 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss @@ -37,13 +37,6 @@ div.tb-widget { flex-direction: row; place-content: flex-start space-between; align-items: flex-start; - &-absolute { - position: absolute; - top: 0; - right: 0; - left: 0; - z-index: 1; - } } .tb-widget-title { @@ -59,23 +52,24 @@ div.tb-widget { tb-timewindow { font-size: 14px; opacity: .85; - margin: 0; } - .title { + .title-row { + display: flex; + flex-direction: row; + place-content: center flex-start; + align-items: center; + gap: 4px; width: 100%; + } + + .title { overflow: hidden; text-overflow: ellipsis; + white-space: nowrap; line-height: 24px; - letter-spacing: .01em; + letter-spacing: normal; margin: 0; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - - &.single-row{ - -webkit-line-clamp: 1; - } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.ts index 41f3fe7e4f..bc298734fc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.ts @@ -77,13 +77,15 @@ export class WidgetPreviewComponent extends PageComponent implements OnInit, OnC } private loadPreviewWidget() { - const widget = deepClone(this.widget); - widget.sizeX = 24; - widget.sizeY = this.widget.sizeY * 2; - widget.row = 0; - widget.col = 0; - widget.config = this.widgetConfig; - this.widgets = [widget]; + if (this.widget) { + const widget = deepClone(this.widget); + widget.sizeX = 24; + widget.sizeY = this.widget.sizeY * 2; + widget.row = 0; + widget.col = 0; + widget.config = this.widgetConfig; + this.widgets = [widget]; + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index c276999d83..fe8bcd78c1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -32,6 +32,7 @@ import { Optional, Renderer2, SimpleChanges, + TemplateRef, Type, ViewChild, ViewContainerRef, @@ -125,6 +126,9 @@ import { IModulesMap } from '@modules/common/modules-map.models'; }) export class WidgetComponent extends PageComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy { + @Input() + widgetTitlePanel: TemplateRef; + @Input() isEdit: boolean; @@ -308,9 +312,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.loadFromWidgetInfo(); } ); - setTimeout(() => { - this.dashboardWidget.updateWidgetParams(); - }, 0); const noDataDisplayMessage = this.widget.config.noDataDisplayMessage; if (isNotEmptyStr(noDataDisplayMessage)) { @@ -387,7 +388,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.handleWidgetException(e); } } - this.widgetContext.destroyed = true; + this.widgetContext.destroy(); this.destroyDynamicWidgetComponent(); } } @@ -409,7 +410,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI elem.classList.add(this.widgetContext.widgetNamespace); this.widgetType = this.widgetInfo.widgetTypeFunction; this.typeParameters = this.widgetInfo.typeParameters; - this.widgetContext.absoluteHeader = this.typeParameters.absoluteHeader; + this.widgetContext.embedTitlePanel = this.typeParameters.embedTitlePanel; if (!this.widgetType) { this.widgetTypeInstance = {}; @@ -480,6 +481,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } if (!this.widgetContext.inited && this.isReady()) { this.widgetContext.inited = true; + this.dashboardWidget.updateWidgetParams(); this.widgetContext.detectContainerChanges(); if (this.cafs.init) { this.cafs.init(); @@ -493,7 +495,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI if (this.dataUpdatePending) { this.widgetTypeInstance.onDataUpdated(); setTimeout(() => { - this.dashboardWidget.updateCustomHeaderActions(true); + this.dashboardWidget.updateParamsFromData(true); }, 0); this.dataUpdatePending = false; } @@ -735,6 +737,10 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI { provide: 'errorMessages', useValue: this.errorMessages + }, + { + provide: 'widgetTitlePanel', + useValue: this.widgetTitlePanel } ], parent: this.injector @@ -745,7 +751,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.widgetContext.$containerParent = $(containerElement); try { - this.dynamicWidgetComponentRef = this.widgetContentContainer.createComponent(this.widgetInfo.componentFactory, 0, injector); + this.dynamicWidgetComponentRef = this.widgetContentContainer.createComponent(this.widgetInfo.componentType, + {index: 0, injector, ngModuleRef: this.widgetInfo.componentModuleRef}); this.cd.detectChanges(); } catch (e) { if (this.dynamicWidgetComponentRef) { @@ -845,7 +852,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI if (this.widgetInstanceInited) { this.widgetTypeInstance.onDataUpdated(); setTimeout(() => { - this.dashboardWidget.updateCustomHeaderActions(true); + this.dashboardWidget.updateParamsFromData(true); }, 0); } else { this.dataUpdatePending = true; diff --git a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts index 5bae1ad256..364973d013 100644 --- a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts @@ -33,7 +33,7 @@ import { IAliasController, IStateController } from '@app/core/api/widget-api.mod import { enumerable } from '@shared/decorators/enumerable'; import { UtilsService } from '@core/services/utils.service'; import { TbPopoverComponent } from '@shared/components/popover.component'; -import { ComponentStyle, textStyle } from '@shared/models/widget-settings.models'; +import { ComponentStyle, iconStyle, textStyle } from '@shared/models/widget-settings.models'; export interface WidgetsData { widgets: Array; @@ -331,8 +331,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { margin: string; borderRadius: string; - title: string; - customTranslatedTitle: string; + title$: Observable; titleTooltip: string; showTitle: boolean; titleStyle: ComponentStyle; @@ -431,30 +430,23 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { this.margin = this.widget.config.margin || '0px'; this.borderRadius = this.widget.config.borderRadius; - this.title = isDefined(this.widgetContext.widgetTitle) + const title = isDefined(this.widgetContext.widgetTitle) && this.widgetContext.widgetTitle.length ? this.widgetContext.widgetTitle : this.widget.config.title; - this.customTranslatedTitle = this.dashboard.utils.customTranslation(this.title, this.title); + this.title$ = this.widgetContext.registerLabelPattern('widgetTitle', title); this.titleTooltip = isDefined(this.widgetContext.widgetTitleTooltip) && this.widgetContext.widgetTitleTooltip.length ? this.widgetContext.widgetTitleTooltip : this.widget.config.titleTooltip; this.titleTooltip = this.dashboard.utils.customTranslation(this.titleTooltip, this.titleTooltip); this.showTitle = isDefined(this.widget.config.showTitle) ? this.widget.config.showTitle : true; - this.titleStyle = {...(this.widget.config.titleStyle || {}), ...textStyle(this.widget.config.titleFont, '24px', '0.01em')}; + this.titleStyle = {...(this.widget.config.titleStyle || {}), ...textStyle(this.widget.config.titleFont, 'normal')}; if (this.widget.config.titleColor) { this.titleStyle.color = this.widget.config.titleColor; } this.titleIcon = isDefined(this.widget.config.titleIcon) ? this.widget.config.titleIcon : ''; this.showTitleIcon = isDefined(this.widget.config.showTitleIcon) ? this.widget.config.showTitleIcon : false; - this.titleIconStyle = {}; + this.titleIconStyle = this.widget.config.iconSize ? iconStyle(this.widget.config.iconSize) : {}; if (this.widget.config.iconColor) { this.titleIconStyle.color = this.widget.config.iconColor; } - if (this.widget.config.iconSize) { - this.titleIconStyle.width = this.widget.config.iconSize; - this.titleIconStyle.height = this.widget.config.iconSize; - this.titleIconStyle.fontSize = this.widget.config.iconSize; - this.titleIconStyle.lineHeight = this.widget.config.iconSize; - } - this.dropShadow = isDefined(this.widget.config.dropShadow) ? this.widget.config.dropShadow : true; this.enableFullscreen = isDefined(this.widget.config.enableFullscreen) ? this.widget.config.enableFullscreen : true; @@ -497,14 +489,22 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { this.showWidgetActions = !this.widgetContext.hideTitlePanel; - this.updateCustomHeaderActions(); + this.updateParamsFromData(); this.widgetActions = this.widgetContext.widgetActions ? this.widgetContext.widgetActions : []; if (detectChanges) { this.widgetContext.detectContainerChanges(); } } - updateCustomHeaderActions(detectChanges = false) { + updateParamsFromData(detectChanges = false) { + this.widgetContext.updateLabelPatterns(); + const update = this.updateCustomHeaderActions(); + if (update && detectChanges) { + this.widgetContext.detectContainerChanges(); + } + } + + private updateCustomHeaderActions(): boolean { let customHeaderActions: Array; if (this.widgetContext.customHeaderActions) { let data: FormattedData[] = []; @@ -517,10 +517,9 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { } if (!isEqual(this.customHeaderActions, customHeaderActions)) { this.customHeaderActions = customHeaderActions; - if (detectChanges) { - this.widgetContext.detectContainerChanges(); - } + return true; } + return false; } private filterCustomHeaderAction(action: WidgetHeaderAction, data: FormattedData[]): boolean { diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 18c8ccd19e..02086e57ef 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -45,13 +45,13 @@ import { WidgetActionsApi, WidgetSubscriptionApi } from '@core/api/widget-api.models'; -import { ChangeDetectorRef, ComponentFactory, Injector, NgZone, Type } from '@angular/core'; +import { ChangeDetectorRef, Injector, NgModuleRef, NgZone, Type } from '@angular/core'; import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { RafService } from '@core/services/raf.service'; import { WidgetTypeId } from '@shared/models/id/widget-type-id'; import { TenantId } from '@shared/models/id/tenant-id'; import { WidgetLayout } from '@shared/models/dashboard.models'; -import { formatValue, isDefined } from '@core/utils'; +import { createLabelFromDatasource, formatValue, hasDatasourceLabelsVariables, isDefined } from '@core/utils'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { @@ -87,10 +87,12 @@ import * as RxJS from 'rxjs'; import * as RxJSOperators from 'rxjs/operators'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { EntityId } from '@shared/models/id/entity-id'; -import { AlarmQuery, AlarmSearchStatus, AlarmStatus} from '@app/shared/models/alarm.models'; +import { AlarmQuery, AlarmSearchStatus, AlarmStatus } from '@app/shared/models/alarm.models'; import { MillisecondsToTimeStringPipe, TelemetrySubscriber } from '@app/shared/public-api'; import { UserId } from '@shared/models/id/user-id'; import { UserSettingsService } from '@core/http/user-settings.service'; +import { DynamicComponentModule } from '@core/services/dynamic-component-factory.service'; +import { BehaviorSubject, Observable } from 'rxjs'; export interface IWidgetAction { name: string; @@ -200,6 +202,8 @@ export class WidgetContext { subscriptions: {[id: string]: IWidgetSubscription} = {}; defaultSubscription: IWidgetSubscription = null; + labelPatterns: {[id: string]: LabelVariablePattern} = {}; + timewindowFunctions: TimewindowFunctions = { onUpdateTimewindow: (startTimeMs, endTimeMs, interval) => { if (this.defaultSubscription) { @@ -265,7 +269,7 @@ export class WidgetContext { hiddenData?: Array<{data: DataSet}>; timeWindow?: WidgetTimewindow; - absoluteHeader?: boolean; + embedTitlePanel?: boolean; hideTitlePanel = false; @@ -312,6 +316,23 @@ export class WidgetContext { }); } + registerLabelPattern(id: string, label: string): Observable { + let labelPattern = this.labelPatterns[id]; + if (labelPattern) { + labelPattern.setupPattern(label); + } else { + labelPattern = new LabelVariablePattern(label, this); + this.labelPatterns[id] = labelPattern; + } + return labelPattern.label$; + } + + updateLabelPatterns() { + for (const key of Object.keys(this.labelPatterns)) { + this.labelPatterns[key].update(); + } + } + showSuccessToast(message: string, duration: number = 1000, verticalPosition: NotificationVerticalPosition = 'bottom', horizontalPosition: NotificationHorizontalPosition = 'left', @@ -406,6 +427,14 @@ export class WidgetContext { this.widgetActions = undefined; } + destroy() { + for (const key of Object.keys(this.labelPatterns)) { + this.labelPatterns[key].destroy(); + } + this.labelPatterns = {}; + this.destroyed = true; + } + closeDialog(resultData: any = null) { const dialogRef = this.$scope.dialogRef || this.stateController.dashboardCtrl.dashboardCtx.getDashboard().dialogRef; if (dialogRef) { @@ -426,6 +455,41 @@ export class WidgetContext { } } +export class LabelVariablePattern { + + private pattern: string; + private hasVariables: boolean; + + private labelSubject = new BehaviorSubject(''); + + public label$ = this.labelSubject.asObservable(); + + constructor(label: string, + private ctx: WidgetContext) { + this.setupPattern(label); + } + + setupPattern(label: string) { + this.pattern = this.ctx.dashboard.utils.customTranslation(label, label); + this.hasVariables = hasDatasourceLabelsVariables(this.pattern); + this.update(); + } + + update() { + let label = this.pattern; + if (this.hasVariables && this.ctx.defaultSubscription?.datasources?.length) { + label = createLabelFromDatasource(this.ctx.defaultSubscription.datasources[0], label); + } + if (this.labelSubject.value !== label) { + this.labelSubject.next(label); + } + } + + destroy() { + this.labelSubject.complete(); + } +} + export interface IDynamicWidgetComponent { readonly ctx: WidgetContext; readonly errorMessages: string[]; @@ -446,7 +510,8 @@ export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescri typeLatestDataKeySettingsSchema?: string | any; image?: string; description?: string; - componentFactory?: ComponentFactory; + componentType?: Type; + componentModuleRef?: NgModuleRef; } export interface WidgetConfigComponentData { diff --git a/ui-ngx/src/app/shared/components/color-input.component.ts b/ui-ngx/src/app/shared/components/color-input.component.ts index 2f0b7b1dae..834fcb8176 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.ts +++ b/ui-ngx/src/app/shared/components/color-input.component.ts @@ -153,10 +153,10 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro $event.stopPropagation(); this.dialogs.colorPicker(this.colorFormGroup.get('color').value, this.colorClearButton).subscribe( - (color) => { - if (color) { + (result) => { + if (!result?.canceled) { this.colorFormGroup.patchValue( - {color}, {emitEvent: true} + {color: result?.color}, {emitEvent: true} ); this.cd.markForCheck(); } diff --git a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts index 06d24f026b..38fcedf406 100644 --- a/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/color-picker-dialog.component.ts @@ -26,12 +26,17 @@ export interface ColorPickerDialogData { colorClearButton: boolean; } +export interface ColorPickerDialogResult { + color?: string; + canceled?: boolean; +} + @Component({ selector: 'tb-color-picker-dialog', templateUrl: './color-picker-dialog.component.html', styleUrls: ['./color-picker-dialog.component.scss'] }) -export class ColorPickerDialogComponent extends DialogComponent { +export class ColorPickerDialogComponent extends DialogComponent { color: string; colorClearButton: boolean; @@ -39,18 +44,18 @@ export class ColorPickerDialogComponent extends DialogComponent, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: ColorPickerDialogData, - public dialogRef: MatDialogRef) { + public dialogRef: MatDialogRef) { super(store, router, dialogRef); this.color = data.color; this.colorClearButton = data.colorClearButton; } selectColor(color: string) { - this.dialogRef.close(color); + this.dialogRef.close({color}); } cancel(): void { - this.dialogRef.close(null); + this.dialogRef.close({canceled: true}); } } diff --git a/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.html index 1051be6525..632e9216be 100644 --- a/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.html @@ -23,6 +23,7 @@ close
diff --git a/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.ts index b6321c966d..53aec6d74f 100644 --- a/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.ts @@ -23,6 +23,12 @@ import { DialogComponent } from '@shared/components/dialog.component'; export interface MaterialIconsDialogData { icon: string; + iconClearButton: boolean; +} + +export interface MaterialIconsDialogResult { + icon?: string; + canceled?: boolean; } @Component({ @@ -31,24 +37,26 @@ export interface MaterialIconsDialogData { providers: [], styleUrls: ['./material-icons-dialog.component.scss'] }) -export class MaterialIconsDialogComponent extends DialogComponent { +export class MaterialIconsDialogComponent extends DialogComponent { selectedIcon: string; + iconClearButton: boolean; constructor(protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: MaterialIconsDialogData, - public dialogRef: MatDialogRef) { + public dialogRef: MatDialogRef) { super(store, router, dialogRef); this.selectedIcon = data.icon; + this.iconClearButton = data.iconClearButton; } selectIcon(icon: string) { - this.dialogRef.close(icon); + this.dialogRef.close({icon}); } cancel(): void { - this.dialogRef.close(null); + this.dialogRef.close({canceled: true}); } } diff --git a/ui-ngx/src/app/shared/components/icon.component.ts b/ui-ngx/src/app/shared/components/icon.component.ts index d1e2c6ddcd..fcc7b70081 100644 --- a/ui-ngx/src/app/shared/components/icon.component.ts +++ b/ui-ngx/src/app/shared/components/icon.component.ts @@ -120,7 +120,7 @@ export class TbIconComponent extends _TbIconBase this._contentChanges = this.contentObserver.observe(this._iconNameContent.nativeElement) .subscribe(() => { const content = this.viewValue; - if (content && this.icon !== content) { + if (this.icon !== content) { this.icon = content; this._updateIcon(); } diff --git a/ui-ngx/src/app/shared/components/json-form/json-form.component.ts b/ui-ngx/src/app/shared/components/json-form/json-form.component.ts index 6ed1d133ae..6da145d826 100644 --- a/ui-ngx/src/app/shared/components/json-form/json-form.component.ts +++ b/ui-ngx/src/app/shared/components/json-form/json-form.component.ts @@ -216,9 +216,9 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato private onColorClick(key: (string | number)[], val: tinycolor.ColorFormats.RGBA, colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) { - this.dialogs.colorPicker(tinycolor(val).toRgbString()).subscribe((color) => { - if (color && colorSelectedFn) { - colorSelectedFn(tinycolor(color).toRgb()); + this.dialogs.colorPicker(tinycolor(val).toRgbString()).subscribe((result) => { + if (!result?.canceled && colorSelectedFn) { + colorSelectedFn(tinycolor(result?.color).toRgb()); } }); } @@ -226,9 +226,9 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato private onIconClick(key: (string | number)[], val: string, iconSelectedFn: (icon: string) => void) { - this.dialogs.materialIconPicker(val).subscribe((icon) => { - if (icon && iconSelectedFn) { - iconSelectedFn(icon); + this.dialogs.materialIconPicker(val).subscribe((result) => { + if (!result?.canceled && iconSelectedFn) { + iconSelectedFn(result?.icon); } }); } diff --git a/ui-ngx/src/app/shared/components/markdown.component.ts b/ui-ngx/src/app/shared/components/markdown.component.ts index cd03959fad..7936d2f51a 100644 --- a/ui-ngx/src/app/shared/components/markdown.component.ts +++ b/ui-ngx/src/app/shared/components/markdown.component.ts @@ -17,7 +17,6 @@ import { ChangeDetectorRef, Component, - ComponentFactory, ComponentRef, ElementRef, EventEmitter, @@ -91,7 +90,7 @@ export class TbMarkdownComponent implements OnChanges { error = null; private tbMarkdownInstanceComponentRef: ComponentRef; - private tbMarkdownInstanceComponentFactory: ComponentFactory; + private tbMarkdownInstanceComponentType: Type; constructor(private help: HelpService, private cd: ChangeDetectorRef, @@ -153,7 +152,7 @@ export class TbMarkdownComponent implements OnChanges { if (this.additionalCompileModules) { compileModules = compileModules.concat(this.additionalCompileModules); } - this.dynamicComponentFactoryService.createDynamicComponentFactory( + this.dynamicComponentFactoryService.createDynamicComponent( class TbMarkdownInstance { ngOnDestroy(): void { parent.destroyMarkdownInstanceResources(); @@ -162,12 +161,13 @@ export class TbMarkdownComponent implements OnChanges { template, compileModules, true, 1, styles - ).subscribe((factory) => { - this.tbMarkdownInstanceComponentFactory = factory; + ).subscribe((componentData) => { + this.tbMarkdownInstanceComponentType = componentData.componentType; const injector: Injector = Injector.create({providers: [], parent: this.markdownContainer.injector}); try { this.tbMarkdownInstanceComponentRef = - this.markdownContainer.createComponent(this.tbMarkdownInstanceComponentFactory, 0, injector); + this.markdownContainer.createComponent(this.tbMarkdownInstanceComponentType, + {index: 0, injector, ngModuleRef: componentData.componentModuleRef}); if (this.context) { for (const propName of Object.keys(this.context)) { this.tbMarkdownInstanceComponentRef.instance[propName] = this.context[propName]; @@ -261,9 +261,9 @@ export class TbMarkdownComponent implements OnChanges { } private destroyMarkdownInstanceResources() { - if (this.tbMarkdownInstanceComponentFactory) { - this.dynamicComponentFactoryService.destroyDynamicComponentFactory(this.tbMarkdownInstanceComponentFactory); - this.tbMarkdownInstanceComponentFactory = null; + if (this.tbMarkdownInstanceComponentType) { + this.dynamicComponentFactoryService.destroyDynamicComponent(this.tbMarkdownInstanceComponentType); + this.tbMarkdownInstanceComponentType = null; } this.tbMarkdownInstanceComponentRef = null; } diff --git a/ui-ngx/src/app/shared/components/material-icon-select.component.ts b/ui-ngx/src/app/shared/components/material-icon-select.component.ts index 740bb4545a..255aeb0668 100644 --- a/ui-ngx/src/app/shared/components/material-icon-select.component.ts +++ b/ui-ngx/src/app/shared/components/material-icon-select.component.ts @@ -54,17 +54,9 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit @Input() disabled: boolean; - private iconClearButtonValue: boolean; - get iconClearButton(): boolean { - return this.iconClearButtonValue; - } @Input() - set iconClearButton(value: boolean) { - const newVal = coerceBooleanProperty(value); - if (this.iconClearButtonValue !== newVal) { - this.iconClearButtonValue = newVal; - } - } + @coerceBoolean() + iconClearButton = false; private requiredValue: boolean; get required(): boolean { @@ -135,11 +127,12 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit openIconDialog() { if (!this.disabled) { - this.dialogs.materialIconPicker(this.materialIconFormGroup.get('icon').value).subscribe( - (icon) => { - if (icon) { + this.dialogs.materialIconPicker(this.materialIconFormGroup.get('icon').value, + this.iconClearButton).subscribe( + (result) => { + if (!result?.canceled) { this.materialIconFormGroup.patchValue( - {icon}, {emitEvent: true} + {icon: result?.icon}, {emitEvent: true} ); this.cd.markForCheck(); } @@ -159,7 +152,8 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit const materialIconsPopover = this.popoverService.displayPopover(trigger, this.renderer, this.viewContainerRef, MaterialIconsComponent, 'left', true, null, { - selectedIcon: this.materialIconFormGroup.get('icon').value + selectedIcon: this.materialIconFormGroup.get('icon').value, + iconClearButton: this.iconClearButton }, {}, {}, {}, true); diff --git a/ui-ngx/src/app/shared/components/material-icons.component.html b/ui-ngx/src/app/shared/components/material-icons.component.html index d31a73ddb5..79476572f6 100644 --- a/ui-ngx/src/app/shared/components/material-icons.component.html +++ b/ui-ngx/src/app/shared/components/material-icons.component.html @@ -62,4 +62,13 @@
{{ 'icon.no-icons-found' | translate:{iconSearch: searchIconControl.value} }}
+
+ +
diff --git a/ui-ngx/src/app/shared/components/material-icons.component.scss b/ui-ngx/src/app/shared/components/material-icons.component.scss index 23b959d118..8cf84e0687 100644 --- a/ui-ngx/src/app/shared/components/material-icons.component.scss +++ b/ui-ngx/src/app/shared/components/material-icons.component.scss @@ -58,4 +58,12 @@ margin: 0; } } + .tb-material-icons-panel-buttons { + height: 40px; + display: flex; + flex-direction: row; + gap: 16px; + align-items: flex-start; + align-self: flex-start; + } } diff --git a/ui-ngx/src/app/shared/components/material-icons.component.ts b/ui-ngx/src/app/shared/components/material-icons.component.ts index 9347243af2..1d826816b7 100644 --- a/ui-ngx/src/app/shared/components/material-icons.component.ts +++ b/ui-ngx/src/app/shared/components/material-icons.component.ts @@ -36,6 +36,7 @@ import { ResourcesService } from '@core/services/resources.service'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { BreakpointObserver } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ selector: 'tb-material-icons', @@ -52,6 +53,10 @@ export class MaterialIconsComponent extends PageComponent implements OnInit { @Input() selectedIcon: string; + @Input() + @coerceBoolean() + iconClearButton = false; + @Input() popover: TbPopoverComponent; @@ -123,6 +128,10 @@ export class MaterialIconsComponent extends PageComponent implements OnInit { this.iconSelected.emit(icon.name); } + clearIcon() { + this.iconSelected.emit(null); + } + private calculatePanelSize(iconsRowSize: number, iconRows = 4) { this.iconsPanelHeight = Math.min(iconRows * this.iconsRowHeight, 10 * this.iconsRowHeight) + 'px'; this.iconsPanelWidth = (iconsRowSize * 36 + (iconsRowSize - 1) * 12 + 6) + 'px'; diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.html b/ui-ngx/src/app/shared/components/time/timewindow.component.html index e445e99c98..a53ea656fc 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.html @@ -41,26 +41,16 @@
- - + [class]="{'no-padding': noPadding}" + matTooltip="{{ 'timewindow.edit' | translate }}" + [matTooltipPosition]="tooltipPosition" + [style]="timewindowComponentStyle" + (click)="toggleTimewindow($event)"> + {{ computedTimewindowStyle.icon }} +
{{innerValue?.displayValue}} | {{innerValue?.displayTimezoneAbbr}} - - +
+ {{ computedTimewindowStyle.icon }}
diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.scss b/ui-ngx/src/app/shared/components/time/timewindow.component.scss index af3feec6eb..a00fe5bdc1 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.scss @@ -24,15 +24,24 @@ max-width: 100%; } section.tb-timewindow { - min-height: 32px; padding: 0 8px; + &.no-padding { + padding: 0; + } + line-height: 32px; + pointer-events: all; + cursor: pointer; + display: flex; + flex-direction: row; + place-content: center flex-start; + align-items: center; + gap: 4px; + width: 100%; - span { + .tb-timewindow-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - pointer-events: all; - cursor: pointer; } .timezone-abbr { diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.ts b/ui-ngx/src/app/shared/components/time/timewindow.component.ts index ff39e54fc7..ba8b4ba114 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.ts @@ -20,7 +20,7 @@ import { ElementRef, forwardRef, HostBinding, Injector, - Input, + Input, OnChanges, OnInit, SimpleChanges, StaticProvider, ViewContainerRef } from '@angular/core'; @@ -50,6 +50,12 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { ComponentPortal } from '@angular/cdk/portal'; import { coerceBoolean } from '@shared/decorators/coercion'; +import { + ComponentStyle, + defaultTimewindowStyle, iconStyle, + textStyle, + TimewindowStyle +} from '@shared/models/widget-settings.models'; // @dynamic @Component({ @@ -64,7 +70,7 @@ import { coerceBoolean } from '@shared/decorators/coercion'; } ] }) -export class TimewindowComponent implements ControlValueAccessor { +export class TimewindowComponent implements ControlValueAccessor, OnInit, OnChanges { historyOnlyValue = false; @@ -88,6 +94,14 @@ export class TimewindowComponent implements ControlValueAccessor { @coerceBoolean() noMargin = false; + @Input() + @coerceBoolean() + noPadding = false; + + @Input() + @coerceBoolean() + disablePanel = false; + @Input() @coerceBoolean() forAllTimeEnabled = false; @@ -145,10 +159,10 @@ export class TimewindowComponent implements ControlValueAccessor { } @Input() - direction: 'left' | 'right' = 'left'; + tooltipPosition: TooltipPosition = 'above'; @Input() - tooltipPosition: TooltipPosition = 'above'; + timewindowStyle: TimewindowStyle; @Input() @coerceBoolean() @@ -158,6 +172,10 @@ export class TimewindowComponent implements ControlValueAccessor { timewindowDisabled: boolean; + computedTimewindowStyle: TimewindowStyle; + timewindowComponentStyle: ComponentStyle; + timewindowIconStyle: ComponentStyle; + private propagateChange = (_: any) => {}; constructor(private overlay: Overlay, @@ -170,10 +188,28 @@ export class TimewindowComponent implements ControlValueAccessor { public viewContainerRef: ViewContainerRef) { } + ngOnInit() { + this.updateTimewindowStyle(); + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (propName === 'timewindowStyle') { + this.updateTimewindowStyle(); + } + } + } + } + toggleTimewindow($event: Event) { if ($event) { $event.stopPropagation(); } + if (this.disablePanel) { + return; + } const config = new OverlayConfig({ panelClass: 'tb-timewindow-panel', backdropClass: 'cdk-overlay-transparent-backdrop', @@ -226,6 +262,17 @@ export class TimewindowComponent implements ControlValueAccessor { this.cd.detectChanges(); } + private updateTimewindowStyle() { + if (!this.asButton) { + this.computedTimewindowStyle = {...defaultTimewindowStyle, ...(this.timewindowStyle || {})}; + this.timewindowComponentStyle = textStyle(this.computedTimewindowStyle.font); + if (this.computedTimewindowStyle.color) { + this.timewindowComponentStyle.color = this.computedTimewindowStyle.color; + } + this.timewindowIconStyle = this.computedTimewindowStyle.iconSize ? iconStyle(this.computedTimewindowStyle.iconSize) : {}; + } + } + private onHistoryOnlyChanged(): boolean { if (this.historyOnlyValue && this.innerValue && this.innerValue.selectedTab !== TimewindowType.HISTORY) { this.innerValue.selectedTab = TimewindowType.HISTORY; diff --git a/ui-ngx/src/app/shared/models/widget-settings.models.ts b/ui-ngx/src/app/shared/models/widget-settings.models.ts index f7c1b6746e..18782cdd93 100644 --- a/ui-ngx/src/app/shared/models/widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/widget-settings.models.ts @@ -60,6 +60,7 @@ export interface Font { family: string; weight: fontWeight; style: fontStyle; + lineHeight: string; } export enum ColorType { @@ -89,6 +90,22 @@ export interface ColorSettings { colorFunction?: string; } +export interface TimewindowStyle { + showIcon: boolean; + icon: string; + iconSize: string; + iconPosition: 'left' | 'right'; + font?: Font; + color?: string; +} + +export const defaultTimewindowStyle: TimewindowStyle = { + showIcon: true, + icon: 'query_builder', + iconSize: '24px', + iconPosition: 'left' +}; + export const constantColor = (color: string): ColorSettings => ({ type: ColorType.constant, color, @@ -298,19 +315,19 @@ export interface BackgroundSettings { overlay: OverlaySettings; } -export const iconStyle = (size: number, sizeUnit: cssUnit): ComponentStyle => { - const iconSize = size + sizeUnit; +export const iconStyle = (size: number | string, sizeUnit: cssUnit = 'px'): ComponentStyle => { + const iconSize = typeof size === 'number' ? size + sizeUnit : size; return { width: iconSize, + minWidth: iconSize, height: iconSize, fontSize: iconSize, lineHeight: iconSize }; }; -export const textStyle = (font?: Font, lineHeight = '1.5', letterSpacing = '0.25px'): ComponentStyle => { +export const textStyle = (font?: Font, letterSpacing = 'normal'): ComponentStyle => { const style: ComponentStyle = { - lineHeight, letterSpacing }; if (font?.style) { @@ -319,6 +336,9 @@ export const textStyle = (font?: Font, lineHeight = '1.5', letterSpacing = '0.25 if (font?.weight) { style.fontWeight = font.weight; } + if (font?.lineHeight) { + style.lineHeight = font.lineHeight; + } if (font?.size) { style.fontSize = (font.size + (font.sizeUnit || 'px')); } diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index c3b23048d1..3a566d8b9c 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -40,7 +40,7 @@ import { Dashboard } from '@shared/models/dashboard.models'; import { IAliasController } from '@core/api/widget-api.models'; import { isEmptyStr } from '@core/utils'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; -import { ComponentStyle, Font } from '@shared/models/widget-settings.models'; +import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models'; export enum widgetType { timeseries = 'timeseries', @@ -183,7 +183,7 @@ export interface WidgetTypeParameters { processNoDataByWidget?: boolean; previewWidth?: string; previewHeight?: string; - absoluteHeader?: boolean; + embedTitlePanel?: boolean; } export interface WidgetControllerDescriptor { @@ -633,6 +633,7 @@ export interface WidgetConfig { useDashboardTimewindow?: boolean; displayTimewindow?: boolean; timewindow?: Timewindow; + timewindowStyle?: TimewindowStyle; desktopHide?: boolean; mobileHide?: boolean; mobileHeight?: number; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 026ebf857e..95d5199947 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3902,7 +3902,15 @@ "interval": "Interval", "just-now": "Just now", "just-now-lower": "just now", - "ago": "ago" + "ago": "ago", + "style": "Timewindow style", + "icon": "Icon", + "icon-position": "Icon position", + "icon-position-left": "Left", + "icon-position-right": "Right", + "font": "Font", + "color": "Color", + "preview": "Preview" }, "unit": { "millimeter": "Millimeter", @@ -5799,7 +5807,8 @@ "font-weight-lighter": "Lighter", "color": "Color", "shadow-color": "Shadow color", - "preview": "Preview" + "preview": "Preview", + "line-height": "Line height" }, "home": { "no-data-available": "No data available" From 71fe11d54dc4f83123ae61e5f89896795e259815 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 8 Aug 2023 19:08:40 +0300 Subject: [PATCH 149/166] UI: Fix value cards default config. --- .../src/main/data/json/system/widget_bundles/cards.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index b87f2c7b83..dd25e00442 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -244,7 +244,7 @@ "settingsDirective": "tb-value-card-widget-settings", "hasBasicMode": true, "basicModeDirective": "tb-value-card-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1.6\"},\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null}}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\",\"layout\":\"square\",\"showLabel\":true,\"labelFont\":{\"family\":\"Roboto\",\"size\":16,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\"},\"labelColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"showIcon\":true,\"iconSize\":40,\"iconSizeUnit\":\"px\",\"icon\":\"thermostat\",\"iconColor\":{\"type\":\"constant\",\"color\":\"#5469FF\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"valueFont\":{\"family\":\"Roboto\",\"size\":52,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"showDate\":true,\"dateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"dateFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\"},\"dateColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.38)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1.6\"},\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null}}" } }, { @@ -265,7 +265,7 @@ "settingsDirective": "tb-value-card-widget-settings", "hasBasicMode": true, "basicModeDirective": "tb-value-card-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Horizontal value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"titleFont\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1.6\"},\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null}}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\",\"layout\":\"horizontal\",\"showLabel\":true,\"labelFont\":{\"family\":\"Roboto\",\"size\":16,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\"},\"labelColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"showIcon\":true,\"iconSize\":40,\"iconSizeUnit\":\"px\",\"icon\":\"thermostat\",\"iconColor\":{\"type\":\"constant\",\"color\":\"#5469FF\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"valueFont\":{\"family\":\"Roboto\",\"size\":52,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"showDate\":true,\"dateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"dateFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\"},\"dateColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.38)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1.6\"},\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null}}" } } ] From 32fae3a4b695871fda9013cf4b7f91772d664604 Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 9 Aug 2023 11:33:22 +0300 Subject: [PATCH 150/166] tbel: add gecodeToJson(String.class) --- .../thingsboard/script/api/tbel/TbUtils.java | 5 +++++ .../script/api/tbel/TbUtilsTest.java | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java index b337612011..bffd60f029 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java @@ -57,6 +57,8 @@ public class TbUtils { List.class))); parserConfig.addImport("decodeToJson", new MethodStub(TbUtils.class.getMethod("decodeToJson", ExecutionContext.class, List.class))); + parserConfig.addImport("decodeToJson", new MethodStub(TbUtils.class.getMethod("decodeToJson", + ExecutionContext.class, String.class))); parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes", ExecutionContext.class, String.class))); parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes", @@ -174,6 +176,9 @@ public class TbUtils { public static Object decodeToJson(ExecutionContext ctx, List bytesList) throws IOException { return TbJson.parse(ctx, bytesToString(bytesList)); } + public static Object decodeToJson(ExecutionContext ctx, String jsonStr) throws IOException { + return TbJson.parse(ctx, jsonStr); + } public static String bytesToString(List bytesList) { byte[] bytes = bytesFromList(bytesList); diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java index b6d5395af8..a16a70b962 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java @@ -27,6 +27,7 @@ import org.mvel2.SandboxedParserConfiguration; import org.mvel2.execution.ExecutionArrayList; import org.mvel2.execution.ExecutionHashMap; +import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -348,6 +349,24 @@ public class TbUtilsTest { Assert.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleVaList, 0, false))); } + @Test + public void parseBytesDecodeToJson() throws IOException { + String expectedStr = "{\"hello\": \"world\"}"; + ExecutionHashMap expectedJson = new ExecutionHashMap<>(1, ctx); + expectedJson.put("hello", "world"); + List expectedBytes = TbUtils.stringToBytes(ctx, expectedStr); + Object actualJson = TbUtils.decodeToJson(ctx, expectedBytes); + Assert.assertEquals(expectedJson,actualJson); + } + @Test + public void parseStringDecodeToJson() throws IOException { + String expectedStr = "{\"hello\": \"world\"}"; + ExecutionHashMap expectedJson = new ExecutionHashMap<>(1, ctx); + expectedJson.put("hello", "world"); + Object actualJson = TbUtils.decodeToJson(ctx, expectedStr); + Assert.assertEquals(expectedJson,actualJson); + } + private static List toList(byte[] data) { List result = new ArrayList<>(data.length); for (Byte b : data) { From ba85007a2dab10ba2bb8c0839b551e07c6a2f747 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 9 Aug 2023 10:59:13 +0200 Subject: [PATCH 151/166] minor improvements --- .../server/controller/TelemetryController.java | 10 ++++++---- ui-ngx/src/assets/locale/locale.constant-ca_ES.json | 1 + ui-ngx/src/assets/locale/locale.constant-cs_CZ.json | 1 + ui-ngx/src/assets/locale/locale.constant-da_DK.json | 1 + ui-ngx/src/assets/locale/locale.constant-de_DE.json | 1 + ui-ngx/src/assets/locale/locale.constant-el_GR.json | 1 + ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + ui-ngx/src/assets/locale/locale.constant-es_ES.json | 1 + ui-ngx/src/assets/locale/locale.constant-fa_IR.json | 1 + ui-ngx/src/assets/locale/locale.constant-fr_FR.json | 1 + ui-ngx/src/assets/locale/locale.constant-it_IT.json | 1 + ui-ngx/src/assets/locale/locale.constant-ja_JP.json | 1 + ui-ngx/src/assets/locale/locale.constant-ka_GE.json | 1 + ui-ngx/src/assets/locale/locale.constant-ko_KR.json | 1 + ui-ngx/src/assets/locale/locale.constant-lv_LV.json | 1 + ui-ngx/src/assets/locale/locale.constant-pt_BR.json | 1 + ui-ngx/src/assets/locale/locale.constant-ro_RO.json | 1 + ui-ngx/src/assets/locale/locale.constant-sl_SI.json | 1 + ui-ngx/src/assets/locale/locale.constant-tr_TR.json | 1 + ui-ngx/src/assets/locale/locale.constant-uk_UA.json | 1 + ui-ngx/src/assets/locale/locale.constant-zh_CN.json | 1 + ui-ngx/src/assets/locale/locale.constant-zh_TW.json | 1 + 22 files changed, 27 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 6937fa6c84..2445ee9bb6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -462,7 +462,9 @@ public class TelemetryController extends BaseController { notes = "Delete time-series for selected entity based on entity id, entity type and keys." + " Use 'deleteAllDataForKeys' to delete all time-series data." + " Use 'startTs' and 'endTs' to specify time-range instead. " + - " Use 'rewriteLatestIfDeleted' to rewrite latest value (stored in separate table for performance) after deletion of the time range. " + + " Use 'deleteLatest' to delete latest value (stored in separate table for performance) if the value's timestamp matches the time-range. " + + " Use 'rewriteLatestIfDeleted' to rewrite latest value (stored in separate table for performance) if the value's timestamp matches the time-range and 'deleteLatest' param is true." + + " The replacement value will be fetched from the 'time-series' table, and its timestamp will be the most recent one before the defined time-range. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) @ApiResponses(value = { @@ -486,10 +488,10 @@ public class TelemetryController extends BaseController { @RequestParam(name = "startTs", required = false) Long startTs, @ApiParam(value = "A long value representing the end timestamp of removal time range in milliseconds.") @RequestParam(name = "endTs", required = false) Long endTs, - @ApiParam(value = "If the parameter is set to true, the latest telemetry will be rewritten in case that current latest value was removed, otherwise, in case that parameter is set to false the new latest value will not set.") - @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted, @ApiParam(value = "If the parameter is set to true, the latest telemetry can be removed, otherwise, in case that parameter is set to false the latest value will not removed.") - @RequestParam(name = "deleteLatest", required = false, defaultValue = "true") boolean deleteLatest) throws ThingsboardException { + @RequestParam(name = "deleteLatest", required = false, defaultValue = "true") boolean deleteLatest, + @ApiParam(value = "If the parameter is set to true, the latest telemetry will be rewritten in case that current latest value was removed, otherwise, in case that parameter is set to false the new latest value will not set.") + @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted) throws ThingsboardException { EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted, deleteLatest); } diff --git a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json index 103d55a79c..34735dc1c5 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ca_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-ca_ES.json @@ -633,6 +633,7 @@ "latest-telemetry": "Última telemetria", "attributes-scope": "Abast dels atributs del dispositiu", "scope-telemetry": "Telemetria", + "scope-latest-telemetry": "Última telemetria", "scope-client": "Atributs del Client", "scope-server": "Atributs del Servidor", "scope-shared": "Atributs Compartits", diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index 2a79340240..25325acab7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -446,6 +446,7 @@ "latest-telemetry": "Poslední telemetrie", "attributes-scope": "Rozsah atributů entity", "scope-telemetry": "Telemetrie", + "scope-latest-telemetry": "Poslední telemetrie", "scope-client": "Atributy klienta", "scope-server": "Atributy serveru", "scope-shared": "Sdílené atributy", diff --git a/ui-ngx/src/assets/locale/locale.constant-da_DK.json b/ui-ngx/src/assets/locale/locale.constant-da_DK.json index 7389ef448d..1c26dd45a7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-da_DK.json +++ b/ui-ngx/src/assets/locale/locale.constant-da_DK.json @@ -454,6 +454,7 @@ "latest-telemetry": "Seneste telemetri", "attributes-scope": "Omfang af entitetsattributter", "scope-telemetry": "Telemetri", + "scope-latest-telemetry": "Seneste telemetri", "scope-client": "Klientattributter", "scope-server": "Serverattributter", "scope-shared": "Delte attributter", diff --git a/ui-ngx/src/assets/locale/locale.constant-de_DE.json b/ui-ngx/src/assets/locale/locale.constant-de_DE.json index c27d63f4fb..d7b50d51c3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-de_DE.json +++ b/ui-ngx/src/assets/locale/locale.constant-de_DE.json @@ -325,6 +325,7 @@ "latest-telemetry": "Neueste Telemetrie", "attributes-scope": "Entitätseigenschaftsbereich", "scope-telemetry": "Telemetrie", + "scope-latest-telemetry": "Neueste Telemetrie", "scope-client": "Client Eigenschaften", "scope-server": "Server Eigenschaften", "scope-shared": "Gemeinsame Eigenschaften", diff --git a/ui-ngx/src/assets/locale/locale.constant-el_GR.json b/ui-ngx/src/assets/locale/locale.constant-el_GR.json index 36e8e4e000..9c5ac0db54 100644 --- a/ui-ngx/src/assets/locale/locale.constant-el_GR.json +++ b/ui-ngx/src/assets/locale/locale.constant-el_GR.json @@ -292,6 +292,7 @@ "latest-telemetry": "Τελευταία τηλεμετρία", "attributes-scope": "Πεδίο εφαρμογής Χαρακτηριστικών Οντότητας", "scope-telemetry": "Τηλεμετρία", + "scope-latest-telemetry": "Τελευταία τηλεμετρία", "scope-client": "Χαρακτηριστικά Client", "scope-server": "Χαρακτηριστικά Server", "scope-shared": "Κοινόχρηστα Χαρακτηριστικά", diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 30c05e2edb..76484651b0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -700,6 +700,7 @@ "no-latest-telemetry": "No latest telemetry", "attributes-scope": "Entity attributes scope", "scope-telemetry": "Telemetry", + "scope-latest-telemetry": "Latest telemetry", "scope-client": "Client attributes", "scope-server": "Server attributes", "scope-shared": "Shared attributes", diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index ec4f93823b..cbbaf4b5a1 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -668,6 +668,7 @@ "latest-telemetry": "Última telemetría", "attributes-scope": "Alcance de los atributos del dispositivo", "scope-telemetry": "Telemetría", + "scope-latest-telemetry": "Última telemetría", "scope-client": "Atributos de Cliente", "scope-server": "Atributos de Servidor", "scope-shared": "Atributos Compartidos", diff --git a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json index da841a6e53..6ab40820ec 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json @@ -255,6 +255,7 @@ "latest-telemetry": "آخرين سنجش", "attributes-scope": "حوزه ويژگي هاي موجودي", "scope-telemetry": "تله متری", + "scope-latest-telemetry": "آخرين سنجش", "scope-client": "ويژگي هاي مشتري", "scope-server": "ويژگي هاي سِروِر", "scope-shared": "ويژگي هاي مشترک", diff --git a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json index c021784db7..3db8795df6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json @@ -460,6 +460,7 @@ "prev-widget": "Widget précédent", "scope-client": "Attributs du client", "scope-telemetry": "Télémétrie", + "scope-latest-telemetry": "Dernière télémétrie", "scope-server": "Attributs du serveur", "scope-shared": "Attributs partagés", "selected-attributes": "{count, plural, =1 {1 attribut} other {# attributs} } sélectionnés", diff --git a/ui-ngx/src/assets/locale/locale.constant-it_IT.json b/ui-ngx/src/assets/locale/locale.constant-it_IT.json index 2c093e76a9..61f6554049 100644 --- a/ui-ngx/src/assets/locale/locale.constant-it_IT.json +++ b/ui-ngx/src/assets/locale/locale.constant-it_IT.json @@ -277,6 +277,7 @@ "latest-telemetry": "Ultima telemetria", "attributes-scope": "Visibilità attributi entità", "scope-telemetry": "Telemetria", + "scope-latest-telemetry": "Ultima telemetria", "scope-client": "Attributi client", "scope-server": "Attributi server", "scope-shared": "Attributi condivisi", diff --git a/ui-ngx/src/assets/locale/locale.constant-ja_JP.json b/ui-ngx/src/assets/locale/locale.constant-ja_JP.json index 23145c1f89..56130aabc3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ja_JP.json +++ b/ui-ngx/src/assets/locale/locale.constant-ja_JP.json @@ -245,6 +245,7 @@ "latest-telemetry": "最新テレメトリ", "attributes-scope": "エンティティ属性のスコープ", "scope-telemetry": "テレメトリー", + "scope-latest-telemetry": "最新テレメトリ", "scope-client": "クライアントの属性", "scope-server": "サーバーの属性", "scope-shared": "共有属性", diff --git a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json index 89d25703e3..55b6e85f66 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json +++ b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json @@ -291,6 +291,7 @@ "latest-telemetry": "უახლესი ტელემეტრია", "attributes-scope": "ობიექტის ატრიბუტების ფარგლები", "scope-telemetry": "ტელემეტრია", + "scope-latest-telemetry": "უახლესი ტელემეტრია", "scope-client": "კლიენტის ატრიბუტები", "scope-server": "სერვერის ატრიბუტები", "scope-shared": "ატრიბუტების გაზიარება", diff --git a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json index c1be51d1db..ae53707a52 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json +++ b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json @@ -409,6 +409,7 @@ "latest-telemetry": "최근 데이터", "attributes-scope": "장치 속성 범위", "scope-telemetry": "원격 측정", + "scope-latest-telemetry": "최근 데이터", "scope-client": "클라이언트 속성", "scope-server": "서버 속성", "scope-shared": "공유 속성", diff --git a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json index f4f5befc14..00e6be4714 100644 --- a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json +++ b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json @@ -257,6 +257,7 @@ "latest-telemetry": "Jaunākā telemetrija", "attributes-scope": "Vienības atribūtu darbības joma", "scope-telemetry": "Telemetrija", + "scope-latest-telemetry": "Jaunākā telemetrija", "scope-client": "Klientu atribūti", "scope-server": "Servera atribūti", "scope-shared": "Dalītie atribūti", diff --git a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json index 28c7082df9..12ad7743c4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json +++ b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json @@ -310,6 +310,7 @@ "latest-telemetry": "Última telemetria", "attributes-scope": "Escopo de atributos de entidade", "scope-telemetry": "Telemetria", + "scope-latest-telemetry": "Última telemetria", "scope-client": "Atributos do cliente", "scope-server": "Atributos do servidor", "scope-shared": "Atributos compartilhados", diff --git a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json index da0012e6f6..4b565d8311 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json +++ b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json @@ -286,6 +286,7 @@ "latest-telemetry": "Ultimele Date Telemetrice", "attributes-scope": "Scop Atribute Entitate", "scope-telemetry": "Telemetrie", + "scope-latest-telemetry": "Ultimele Date Telemetrice", "scope-client": "Atribute Client", "scope-server": "Atribute Server", "scope-shared": "Atribute Partajate", diff --git a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json index 7875163b60..0515b5890e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json +++ b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json @@ -409,6 +409,7 @@ "latest-telemetry": "Najnovejša telemetrija", "attributes-scope": "Obseg atributov entitete", "scope-telemetry": "Telemetrija", + "scope-latest-telemetry": "Najnovejša telemetrija", "scope-client": "Atributi odjemalca", "scope-server": "Atributi strežnika", "scope-shared": "Skupni atributi", diff --git a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json index c56be48c74..8590dd2768 100644 --- a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json +++ b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json @@ -446,6 +446,7 @@ "latest-telemetry": "Son telemetri", "attributes-scope": "Varlık öznitelik kapsamı", "scope-telemetry": "telemetri", + "scope-latest-telemetry": "Son telemetri", "scope-client": "İstemci öznitelikler", "scope-server": "Sunucu öznitelikler", "scope-shared": "Paylaşılan öznitelikler", diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json index bd608cd709..c7afaad234 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -343,6 +343,7 @@ "latest-telemetry": "Остання телеметрія", "attributes-scope": "Область видимості атрибутів", "scope-telemetry": "Телеметрія", + "scope-latest-telemetry": "Остання телеметрія", "scope-client": "Клієнтські атрибути", "scope-server": "Серверні атрибути", "scope-shared": "Спільні атрибути", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index 2fd73b32bd..6e5bf9a6b6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -591,6 +591,7 @@ "latest-telemetry": "最新遥测数据", "attributes-scope": "设备属性范围", "scope-telemetry": "遥测", + "scope-latest-telemetry": "最新遥测数据", "scope-client": "客户端属性", "scope-server": "服务端属性", "scope-shared": "共享属性", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json index 526597ad70..2b98451f20 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json @@ -520,6 +520,7 @@ "latest-telemetry": "最新遙測", "attributes-scope": "設備屬性範圍", "scope-telemetry": "遙測", + "scope-latest-telemetry": "最新遙測", "scope-client": "客戶端屬性", "scope-server": "服務端屬性", "scope-shared": "共享屬性", From ee5bc97330b92c5ef70f35855ad6e968418040ca Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 9 Aug 2023 13:07:16 +0300 Subject: [PATCH 152/166] UI: Minor improvements --- .../data/json/system/widget_bundles/cards.json | 2 +- .../lib/cards/value-card-widget.component.ts | 2 +- .../value-card-widget-settings.component.html | 2 +- .../common/css-unit-select.component.ts | 4 ++-- .../common/date-format-select.component.ts | 4 ++-- .../home/models/dashboard-component.models.ts | 2 +- .../home/models/widget-component.models.ts | 18 +++++++++--------- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index dd25e00442..858d816338 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -265,7 +265,7 @@ "settingsDirective": "tb-value-card-widget-settings", "hasBasicMode": true, "basicModeDirective": "tb-value-card-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\",\"layout\":\"horizontal\",\"showLabel\":true,\"labelFont\":{\"family\":\"Roboto\",\"size\":16,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\"},\"labelColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"showIcon\":true,\"iconSize\":40,\"iconSizeUnit\":\"px\",\"icon\":\"thermostat\",\"iconColor\":{\"type\":\"constant\",\"color\":\"#5469FF\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"valueFont\":{\"family\":\"Roboto\",\"size\":52,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"showDate\":true,\"dateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"dateFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\"},\"dateColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.38)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1.6\"},\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null}}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"labelPosition\":\"top\",\"layout\":\"horizontal\",\"showLabel\":true,\"labelFont\":{\"family\":\"Roboto\",\"size\":16,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\"},\"labelColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"showIcon\":true,\"iconSize\":40,\"iconSizeUnit\":\"px\",\"icon\":\"thermostat\",\"iconColor\":{\"type\":\"constant\",\"color\":\"#5469FF\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"valueFont\":{\"family\":\"Roboto\",\"size\":52,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"showDate\":true,\"dateFormat\":{\"format\":null,\"lastUpdateAgo\":true,\"custom\":false},\"dateFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\"},\"dateColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.38)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Horizontal value card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1.6\"},\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null}}" } } ] diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts index 5505a1e8dd..48d432bcb7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts @@ -107,7 +107,7 @@ export class ValueCardWidgetComponent implements OnInit { this.showLabel = this.settings.showLabel; const label = getLabel(this.ctx.datasources); - this.label$ = this.ctx.registerLabelPattern('valueCardLabel', label); + this.label$ = this.ctx.registerLabelPattern(label, this.label$); this.labelStyle = textStyle(this.settings.labelFont, '0.25px'); this.labelColor = ColorProcessor.fromSettings(this.settings.labelColor); this.valueStyle = textStyle(this.settings.valueFont, '0.13px'); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html index 73ee11f393..f0fd9cdb16 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html @@ -44,7 +44,7 @@ {{ 'widgets.value-card.icon' | translate }} -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts index 3d2658dae1..61a6bc4865 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-unit-select.component.ts @@ -67,9 +67,9 @@ export class CssUnitSelectComponent implements OnInit, ControlValueAccessor { setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { - this.cssUnitFormControl.disable(); + this.cssUnitFormControl.disable({emitEvent: false}); } else { - this.cssUnitFormControl.enable(); + this.cssUnitFormControl.enable({emitEvent: false}); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts index 6393a92054..e4f0b47946 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts @@ -90,9 +90,9 @@ export class DateFormatSelectComponent implements OnInit, ControlValueAccessor { setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { - this.dateFormatFormControl.disable(); + this.dateFormatFormControl.disable({emitEvent: false}); } else { - this.dateFormatFormControl.enable(); + this.dateFormatFormControl.enable({emitEvent: false}); } } diff --git a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts index 364973d013..8104e80e36 100644 --- a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts @@ -432,7 +432,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { const title = isDefined(this.widgetContext.widgetTitle) && this.widgetContext.widgetTitle.length ? this.widgetContext.widgetTitle : this.widget.config.title; - this.title$ = this.widgetContext.registerLabelPattern('widgetTitle', title); + this.title$ = this.widgetContext.registerLabelPattern(title, this.title$); this.titleTooltip = isDefined(this.widgetContext.widgetTitleTooltip) && this.widgetContext.widgetTitleTooltip.length ? this.widgetContext.widgetTitleTooltip : this.widget.config.titleTooltip; this.titleTooltip = this.dashboard.utils.customTranslation(this.titleTooltip, this.titleTooltip); diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 02086e57ef..11b631dc52 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -202,7 +202,7 @@ export class WidgetContext { subscriptions: {[id: string]: IWidgetSubscription} = {}; defaultSubscription: IWidgetSubscription = null; - labelPatterns: {[id: string]: LabelVariablePattern} = {}; + labelPatterns = new Map, LabelVariablePattern>(); timewindowFunctions: TimewindowFunctions = { onUpdateTimewindow: (startTimeMs, endTimeMs, interval) => { @@ -316,20 +316,20 @@ export class WidgetContext { }); } - registerLabelPattern(id: string, label: string): Observable { - let labelPattern = this.labelPatterns[id]; + registerLabelPattern(label: string, label$: Observable): Observable { + let labelPattern = label$ ? this.labelPatterns.get(label$) : null; if (labelPattern) { labelPattern.setupPattern(label); } else { labelPattern = new LabelVariablePattern(label, this); - this.labelPatterns[id] = labelPattern; + this.labelPatterns.set(labelPattern.label$, labelPattern); } return labelPattern.label$; } updateLabelPatterns() { - for (const key of Object.keys(this.labelPatterns)) { - this.labelPatterns[key].update(); + for (const labelPattern of this.labelPatterns.values()) { + labelPattern.update(); } } @@ -428,10 +428,10 @@ export class WidgetContext { } destroy() { - for (const key of Object.keys(this.labelPatterns)) { - this.labelPatterns[key].destroy(); + for (const labelPattern of this.labelPatterns.values()) { + labelPattern.destroy(); } - this.labelPatterns = {}; + this.labelPatterns.clear(); this.destroyed = true; } From 2ed3d479520c8303f1a64f8c4fcae1e1b081f6e5 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 9 Aug 2023 14:55:40 +0300 Subject: [PATCH 153/166] UI: Fix widget labels pattern processing. --- ui-ngx/src/app/core/api/widget-api.models.ts | 2 ++ ui-ngx/src/app/core/api/widget-subscription.ts | 10 ++++++++++ .../app/modules/home/models/widget-component.models.ts | 5 +++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index 115bf6e222..d0de3481ed 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -284,6 +284,8 @@ export interface IWidgetSubscription { legendData: LegendData; + readonly firstDatasource?: Datasource; + datasourcePages?: PageData[]; dataPages?: PageData>[]; datasources?: Array; diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index 440e61148e..f9a79d8f76 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -160,6 +160,16 @@ export class WidgetSubscription implements IWidgetSubscription { warnOnPageDataOverflow: boolean; ignoreDataUpdateOnIntervalTick: boolean; + get firstDatasource(): Datasource { + if (this.type === widgetType.alarm) { + return this.alarmSource; + } else if (this.datasources?.length) { + return this.datasources[0]; + } else { + return null; + } + } + datasourcePages: PageData[]; dataPages: PageData>[]; entityDataListeners: Array; diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 11b631dc52..12d3312dbb 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -477,8 +477,9 @@ export class LabelVariablePattern { update() { let label = this.pattern; - if (this.hasVariables && this.ctx.defaultSubscription?.datasources?.length) { - label = createLabelFromDatasource(this.ctx.defaultSubscription.datasources[0], label); + const datasource = this.ctx.defaultSubscription?.firstDatasource; + if (this.hasVariables && datasource) { + label = createLabelFromDatasource(datasource, label); } if (this.labelSubject.value !== label) { this.labelSubject.next(label); From 0612da8ca2623603dae499c3cd75616699f73a33 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Wed, 9 Aug 2023 16:07:30 +0300 Subject: [PATCH 154/166] UI: Fixed layout for clear alarm rule --- .../profile/alarm/device-profile-alarm.component.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss index ad664e3156..1b796ccbda 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss @@ -16,7 +16,8 @@ :host { display: block; .clear-alarm-rule { - max-width: 100%; + min-width: 0; + margin-right: 8px; border: 2px groove rgba(0, 0, 0, .45); border-radius: 4px; padding: 8px; From 141a7ff0e6be9bb132e788c870b6689f84fb8b5b Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 9 Aug 2023 21:21:15 +0200 Subject: [PATCH 155/166] changed recalculate_delay --- application/src/main/resources/thingsboard.yml | 5 ++++- .../server/queue/discovery/ZkDiscoveryService.java | 2 +- msa/vc-executor/src/main/resources/tb-vc-executor.yml | 2 +- transport/coap/src/main/resources/tb-coap-transport.yml | 2 +- transport/http/src/main/resources/tb-http-transport.yml | 2 +- transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml | 2 +- transport/mqtt/src/main/resources/tb-mqtt-transport.yml | 2 +- transport/snmp/src/main/resources/tb-snmp-transport.yml | 2 +- 8 files changed, 11 insertions(+), 8 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 1f16fbc414..5b76175fcd 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -96,7 +96,10 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + # The recalculate_delay property recommended in a microservices architecture setup for rule-engine services. + # This property provides a pause to ensure that when a rule-engine service is restarted, other nodes don't immediately attempt to recalculate their partitions. + # The delay is recommended because the initialization of rule chain actors is time-consuming. Avoiding unnecessary recalculations during a restart can enhance system performance and stability. + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" cluster: stats: diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 44999d016a..e99817de17 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -69,7 +69,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi private Integer zkSessionTimeout; @Value("${zk.zk_dir}") private String zkDir; - @Value("${zk.recalculate_delay:60000}") + @Value("${zk.recalculate_delay:0}") private Long recalculateDelay; protected final ConcurrentHashMap> delayedTasks; diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml index 66c6b4d3da..9e57a35e20 100644 --- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml +++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" queue: type: "${TB_QUEUE_TYPE:kafka}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index f4b5e0bc94..a545759f38 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index f92da86b99..1f042fb131 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -68,7 +68,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 05388473f0..ffe815d441 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index e131788929..f7d0209804 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" cache: type: "${CACHE_TYPE:redis}" diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index a7928eb49f..3ed46dde78 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -41,7 +41,7 @@ zk: session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" - recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:60000}" + recalculate_delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}" cache: type: "${CACHE_TYPE:redis}" From cba324f5bae50233c1110b6fd4696d34f98f53a7 Mon Sep 17 00:00:00 2001 From: kalytka Date: Thu, 10 Aug 2023 16:41:15 +0300 Subject: [PATCH 156/166] UI: Refactoring component for used filter and enrichment rule nodes --- .../relation/relation-filters.component.html | 70 +++++++++---------- .../relation/relation-filters.component.scss | 44 +++--------- .../basic/common/data-key-row.component.html | 2 +- .../common/data-keys-panel.component.html | 4 +- .../add-rule-node-dialog.component.scss | 4 ++ .../rule-node-details.component.scss | 5 +- .../entity/entity-subtype-list.component.html | 13 ++-- .../entity/entity-subtype-list.component.ts | 48 ++++++++----- .../entity/entity-type-list.component.html | 9 ++- .../entity/entity-type-list.component.ts | 57 ++++++++++----- .../components/help-popup.component.html | 10 +-- .../components/help-popup.component.scss | 18 +++++ .../shared/components/help-popup.component.ts | 6 ++ .../relation-type-autocomplete.component.html | 3 +- .../relation-type-autocomplete.component.ts | 24 ++++--- .../string-items-list.component.html | 10 ++- .../assets/locale/locale.constant-en_US.json | 1 + ui-ngx/src/form.scss | 14 +++- ui-ngx/src/styles.scss | 6 ++ 19 files changed, 206 insertions(+), 142 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html index b052a1816c..1f4d6f9377 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html @@ -15,49 +15,47 @@ limitations under the License. --> -
-
-
-
+
+
+
{{ 'relation.type' | translate }}
+
{{ 'entity.entity-types' | translate }}
+
+
+
+
-
-
- - - - -
- +
+
+
+ relation.any-relation
-
- relation.any-relation +
+
-
diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.scss b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.scss index a2d4232f24..648076be4c 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.scss +++ b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.scss @@ -14,43 +14,15 @@ * limitations under the License. */ :host { - .tb-relation-filters { - max-width: calc(100vw - 48px); - margin-top: 2px; - overflow: hidden; - - .container{ - width: 100%; - } - - .map-label { - font-weight: 400; - font-size: 12px; - } - - .body { - max-height: 363px; - overflow: auto; - - .row { - padding-top: 5px; - - .input-block { - border: 1px solid #E0E0E0; - width: 100%; - border-radius: 6px; - padding: 24px; - align-items: center; - } - } - } + .flex-50 { + flex: 1 1 50%; + } - .any-filter{ - margin: 10px 0 20px; - } + .actions-header { + width: 40px + } - .add-button { - margin: 5px 0px 15px; - } + .entity-type-list { + display: flex; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html index 63f936195a..05275e0a70 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+
{{ 'datakey.timeseries' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html index 0b3ba50927..4c2c9a834a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html @@ -18,7 +18,7 @@
{{ panelTitle }}
-
+
datakey.source
datakey.key
datakey.label
@@ -32,7 +32,7 @@ [cdkDropListDisabled]="!dragEnabled" (cdkDropListDropped)="keyDrop($event)">
- + {{ label }} - +
+ +
{{ subtypeListEmptyText | translate }} diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts index 1442b44cfd..a10bf7e3e2 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts @@ -15,7 +15,7 @@ /// import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { Observable, Subscription, throwError } from 'rxjs'; import { map, mergeMap, publishReplay, refCount, share } from 'rxjs/operators'; import { Store } from '@ngrx/store'; @@ -23,14 +23,15 @@ import { AppState } from '@app/core/core.state'; import { TranslateService } from '@ngx-translate/core'; import { EntitySubtype, EntityType } from '@shared/models/entity-type.models'; import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; -import { MatChipInputEvent, MatChipGrid } from '@angular/material/chips'; -import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { MatChipGrid, MatChipInputEvent } from '@angular/material/chips'; import { AssetService } from '@core/http/asset.service'; import { DeviceService } from '@core/http/device.service'; import { EdgeService } from '@core/http/edge.service'; import { EntityViewService } from '@core/http/entity-view.service'; import { BroadcastService } from '@core/services/broadcast.service'; import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { FloatLabelType } from '@angular/material/form-field'; @Component({ selector: 'tb-entity-subtype-list', @@ -46,32 +47,43 @@ import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; }) export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy { - entitySubtypeListFormGroup: UntypedFormGroup; + entitySubtypeListFormGroup: FormGroup; modelValue: Array | null; private requiredValue: boolean; + get required(): boolean { return this.requiredValue; } - @Input() label: string; - @Input() + @coerceBoolean() set required(value: boolean) { - const newVal = coerceBooleanProperty(value); - if (this.requiredValue !== newVal) { - this.requiredValue = newVal; + if (this.requiredValue !== value) { + this.requiredValue = value; this.updateValidators(); } } + @Input() + floatLabel: FloatLabelType = 'auto'; + + @Input() + label: string; + @Input() disabled: boolean; @Input() entityType: EntityType; + @Input() + emptyInputPlaceholder: string; + + @Input() + filledInputPlaceholder: string; + @ViewChild('entitySubtypeInput') entitySubtypeInput: ElementRef; @ViewChild('entitySubtypeAutocomplete') entitySubtypeAutocomplete: MatAutocomplete; @ViewChild('chipList', {static: true}) chipList: MatChipGrid; @@ -102,13 +114,14 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, private deviceService: DeviceService, private edgeService: EdgeService, private entityViewService: EntityViewService, - private fb: UntypedFormBuilder) { + private fb: FormBuilder) { this.entitySubtypeListFormGroup = this.fb.group({ entitySubtypeList: [this.entitySubtypeList, this.required ? [Validators.required] : []], entitySubtype: [null] }); } + updateValidators() { this.entitySubtypeListFormGroup.get('entitySubtypeList').setValidators(this.required ? [Validators.required] : []); this.entitySubtypeListFormGroup.get('entitySubtypeList').updateValueAndValidity(); @@ -122,7 +135,6 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, } ngOnInit() { - switch (this.entityType) { case EntityType.ASSET: this.placeholder = this.required ? this.translate.instant('asset.enter-asset-type') @@ -166,6 +178,13 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, break; } + if (this.emptyInputPlaceholder) { + this.placeholder = this.emptyInputPlaceholder; + } + if (this.filledInputPlaceholder) { + this.secondaryPlaceholder = this.filledInputPlaceholder; + } + this.filteredEntitySubtypeList = this.entitySubtypeListFormGroup.get('entitySubtype').valueChanges .pipe( map(value => value ? value : ''), @@ -225,13 +244,6 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, } this.clear(''); } - - clearChipGrid() { - this.entitySubtypeList = []; - this.modelValue = null; - this.entitySubtypeListFormGroup.get('entitySubtypeList').patchValue([], {emitEvent: true}); - } - remove(entitySubtype: string) { const index = this.entitySubtypeList.indexOf(entitySubtype); if (index >= 0) { diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.html b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.html index 0503ce836b..e9168d1f29 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.html @@ -15,7 +15,11 @@ limitations under the License. --> - + {{ label }} +
+ +
{{ 'entity.entity-type-list-empty' | translate }} diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts index 17f908b2e0..7bb52d0948 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts @@ -15,7 +15,7 @@ /// import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; -import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Observable, of } from 'rxjs'; import { filter, map, mergeMap, share, tap } from 'rxjs/operators'; import { Store } from '@ngrx/store'; @@ -25,9 +25,8 @@ import { AliasEntityType, EntityType, entityTypeTranslations } from '@shared/mod import { EntityService } from '@core/http/entity.service'; import { MatAutocomplete } from '@angular/material/autocomplete'; import { MatChipGrid } from '@angular/material/chips'; -import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { FloatLabelType, SubscriptSizing } from '@angular/material/form-field'; -import { coerceBoolean } from '@shared/decorators/coercion'; +import { FloatLabelType, MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; +import { coerceArray, coerceBoolean } from '@shared/decorators/coercion'; interface EntityTypeInfo { name: string; @@ -48,7 +47,7 @@ interface EntityTypeInfo { }) export class EntityTypeListComponent implements ControlValueAccessor, OnInit, AfterViewInit { - entityTypeListFormGroup: UntypedFormGroup; + entityTypeListFormGroup: FormGroup; modelValue: Array | null; @@ -57,19 +56,28 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af return this.requiredValue; } - @Input() label: string; - - @Input() floatLabel: FloatLabelType = 'auto'; - @Input() + @coerceBoolean() set required(value: boolean) { - const newVal = coerceBooleanProperty(value); - if (this.requiredValue !== newVal) { - this.requiredValue = newVal; + if (this.requiredValue !== value) { + this.requiredValue = value; this.updateValidators(); } } + @Input() + @coerceArray() + additionalClasses: Array; + + @Input() + appearance: MatFormFieldAppearance = 'fill'; + + @Input() + label: string; + + @Input() + floatLabel: FloatLabelType = 'auto'; + @Input() disabled: boolean; @@ -79,6 +87,12 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af @Input() allowedEntityTypes: Array; + @Input() + emptyInputPlaceholder: string; + + @Input() + filledInputPlaceholder: string; + @Input() @coerceBoolean() ignoreAuthorityFilter: boolean; @@ -103,7 +117,7 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af constructor(private store: Store, public translate: TranslateService, private entityService: EntityService, - private fb: UntypedFormBuilder) { + private fb: FormBuilder) { this.entityTypeListFormGroup = this.fb.group({ entityTypeList: [this.entityTypeList, this.required ? [Validators.required] : []], entityType: [null] @@ -123,11 +137,17 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af } ngOnInit() { - - this.placeholder = this.required ? this.translate.instant('entity.enter-entity-type') - : this.translate.instant('entity.any-entity'); - this.secondaryPlaceholder = '+' + this.translate.instant('entity.entity-type'); - + if (this.emptyInputPlaceholder) { + this.placeholder = this.emptyInputPlaceholder; + } else { + this.placeholder = this.required ? this.translate.instant('entity.enter-entity-type') : + this.translate.instant('entity.any-entity'); + } + if (this.filledInputPlaceholder) { + this.secondaryPlaceholder = this.filledInputPlaceholder; + } else { + this.secondaryPlaceholder = '+' + this.translate.instant('entity.entity-type'); + } let entityTypes: Array; if (this.ignoreAuthorityFilter && this.allowedEntityTypes && this.allowedEntityTypes.length) { @@ -250,5 +270,4 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af this.entityTypeInput.nativeElement.focus(); }, 0); } - } diff --git a/ui-ngx/src/app/shared/components/help-popup.component.html b/ui-ngx/src/app/shared/components/help-popup.component.html index 1a9fe5541a..730d054b8b 100644 --- a/ui-ngx/src/app/shared/components/help-popup.component.html +++ b/ui-ngx/src/app/shared/components/help-popup.component.html @@ -30,19 +30,21 @@
-
+
diff --git a/ui-ngx/src/app/shared/components/help-popup.component.scss b/ui-ngx/src/app/shared/components/help-popup.component.scss index 2ec26a3c0f..6be4d9b71c 100644 --- a/ui-ngx/src/app/shared/components/help-popup.component.scss +++ b/ui-ngx/src/app/shared/components/help-popup.component.scss @@ -17,6 +17,9 @@ width: initial; display: inline-block; vertical-align: middle; + &.hint-button { + line-height: 1; + } } .tb-help-popup-button { @@ -65,4 +68,19 @@ vertical-align: middle; } } + &.hint-button { + padding: 2px 3px; + line-height: 1; + &.mat-mdc-outlined-button { + padding: 1px 2px; + } + .mdc-button__label > span { + .mat-icon { + margin-right: 0; + } + .mat-mdc-progress-spinner { + margin-right: 0; + } + } + } } diff --git a/ui-ngx/src/app/shared/components/help-popup.component.ts b/ui-ngx/src/app/shared/components/help-popup.component.ts index 08be61f728..64722348a2 100644 --- a/ui-ngx/src/app/shared/components/help-popup.component.ts +++ b/ui-ngx/src/app/shared/components/help-popup.component.ts @@ -28,6 +28,7 @@ import { TbPopoverService } from '@shared/components/popover.service'; import { PopoverPlacement } from '@shared/components/popover.models'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { isDefinedAndNotNull } from '@core/utils'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ // eslint-disable-next-line @angular-eslint/component-selector @@ -62,6 +63,11 @@ export class HelpPopupComponent implements OnChanges, OnDestroy { popoverVisible = false; popoverReady = true; + + @Input() + @coerceBoolean() + hintMode = false; + triggerSafeHtml: SafeHtml = null; textMode = false; diff --git a/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.html b/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.html index c057f1df09..99a650098e 100644 --- a/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.html @@ -15,7 +15,8 @@ limitations under the License. --> - + {{ label }} ; - @Input() floatLabel: FloatLabelType = 'auto'; + @Input() + appearance: MatFormFieldAppearance = 'fill'; @Input() - set required(value: boolean) { - this.requiredValue = coerceBooleanProperty(value); - } + floatLabel: FloatLabelType = 'auto'; + + @Input() + @coerceBoolean() + required: boolean; @Input() disabled: boolean; diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.html b/ui-ngx/src/app/shared/components/string-items-list.component.html index 4677cbc3de..7416e96870 100644 --- a/ui-ngx/src/app/shared/components/string-items-list.component.html +++ b/ui-ngx/src/app/shared/components/string-items-list.component.html @@ -54,7 +54,15 @@ {{ 'common.not-found' | translate }} - {{ hint }} + + {{ hint }} + + + + +
+ +
{{ requiredText }} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index f90a34fe8e..60213184eb 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2031,6 +2031,7 @@ "entity-types": "Entity types", "entity-type-list": "Entity type list", "any-entity": "Any entity", + "add-entity-type": "Add entity type", "enter-entity-type": "Enter entity type", "no-entities-matching": "No entities matching '{{entity}}' were found.", "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.", diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index bef8bf621e..79f141e9d5 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -307,6 +307,10 @@ } } &.tb-chips { + &.flex { + flex: 1; + width: auto; + } .mat-mdc-text-field-wrapper { &.mdc-text-field--outlined, &:not(.mdc-text-field--outlined) { .mat-mdc-form-field-infix { @@ -357,7 +361,7 @@ } .tb-prompt { - height: 38px; + height: 40px; } } @@ -366,11 +370,19 @@ flex-direction: row; gap: 8px; padding-left: 8px; + padding-right: 8px; place-content: center flex-start; align-items: center; + &.no-padding-right { + padding-right: 0; + } @media #{$mat-gt-md} { gap: 12px; padding-left: 12px; + padding-right: 12px; + &.no-padding-right { + padding-right: 0; + } } &-cell { font-weight: 400; diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index ec6060823d..8e6a9dde75 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -855,6 +855,9 @@ mat-label { svg { vertical-align: inherit; } + &.tb-mat-12 { + @include tb-mat-icon-size(12); + } &.tb-mat-16 { @include tb-mat-icon-size(16); } @@ -1208,4 +1211,7 @@ mat-label { color: inherit; } + .cursor-pointer { + cursor: pointer; + } } From b38165e7455e8761f31e1036f73473483c68c055 Mon Sep 17 00:00:00 2001 From: kalytka Date: Thu, 10 Aug 2023 18:20:58 +0300 Subject: [PATCH 157/166] Add translation --- .../home/components/relation/relation-filters.component.html | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html index 1f4d6f9377..57c5ecf621 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html @@ -42,7 +42,7 @@ mat-icon-button (click)="removeFilter($index)" [disabled]="isLoading$ | async" - matTooltip="{{ 'tb.key-val.remove-mapping-entry' | translate }}" + matTooltip="{{ 'relation.remove-filter' | translate }}" matTooltipPosition="above"> delete diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 60213184eb..ecad69fe24 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3326,6 +3326,7 @@ "delete-from-relations-title": "Are you sure you want to delete { count, plural, =1 {1 relation} other {# relations} }?", "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities.", "remove-relation-filter": "Remove relation filter", + "remove-filter": "Remove filter", "add-relation-filter": "Add relation filter", "any-relation": "Any relation", "relation-filters": "Relation filters", From 7de5e6b08491feb584386f21d70144c459d27a9d Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 11 Aug 2023 12:49:32 +0300 Subject: [PATCH 158/166] updated default config for math node --- .../rule/engine/math/TbMathNodeConfiguration.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java index 1636898b8c..f4fccd3ae6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java @@ -32,9 +32,10 @@ public class TbMathNodeConfiguration implements NodeConfiguration Date: Fri, 11 Aug 2023 16:15:34 +0300 Subject: [PATCH 159/166] replace x with t --- .../thingsboard/rule/engine/math/TbMathNodeConfiguration.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java index f4fccd3ae6..e1a3523cf0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNodeConfiguration.java @@ -33,8 +33,8 @@ public class TbMathNodeConfiguration implements NodeConfiguration Date: Fri, 11 Aug 2023 19:23:51 +0300 Subject: [PATCH 160/166] PROD-2339: fix getFeatureType method to handle RPC server-side response over DTLS --- .../transport/coap/CoapTransportResource.java | 11 +- .../coap/CoapTransportResourceTest.java | 352 ++++++++++++++++++ 2 files changed, 360 insertions(+), 3 deletions(-) create mode 100644 common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index d6958137c7..bd02d9fcc4 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -30,6 +30,7 @@ import org.thingsboard.server.coapserver.TbCoapDtlsSessionInfo; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; @@ -379,12 +380,16 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } - private Optional getFeatureType(Request request) { + protected Optional getFeatureType(Request request) { List uriPath = request.getOptions().getUriPath(); try { - if (uriPath.size() >= FEATURE_TYPE_POSITION) { + int size = uriPath.size(); + if (size >= FEATURE_TYPE_POSITION) { + if (size == FEATURE_TYPE_POSITION && StringUtils.isNumeric(uriPath.get(size - 1))) { + return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 2).toUpperCase())); + } return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); - } else if (uriPath.size() >= FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST) { + } else if (size == FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST) { if (uriPath.contains(DataConstants.PROVISION)) { return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase())); } diff --git a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java new file mode 100644 index 0000000000..666f6c95df --- /dev/null +++ b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java @@ -0,0 +1,352 @@ +/** + * 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.transport.coap; + +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.OptionSet; +import org.eclipse.californium.core.coap.Request; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.thingsboard.server.coapserver.CoapServerService; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; +import org.thingsboard.server.transport.coap.client.CoapClientContext; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class CoapTransportResourceTest { + + private static final String V1 = "v1"; + private static final String API = "api"; + private static final String TELEMETRY = "telemetry"; + private static final String ATTRIBUTES = "attributes"; + private static final String RPC = "rpc"; + private static final String CLAIM = "claim"; + private static final String PROVISION = "provision"; + private static final String GET_ATTRIBUTES_URI_QUERY = "clientKeys=attribute1,attribute2&sharedKeys=shared1,shared2"; + + private static final Random RANDOM = new Random(); + + private CoapTransportResource coapTransportResource; + + @BeforeEach + void setUp() { + + var ctxMock = mock(CoapTransportContext.class); + var coapServerServiceMock = mock(CoapServerService.class); + var transportServiceMock = mock(TransportService.class); + var clientContextMock = mock(CoapClientContext.class); + var schedulerComponentMock = mock(SchedulerComponent.class); + + when(ctxMock.getTransportService()).thenReturn(transportServiceMock); + when(ctxMock.getClientContext()).thenReturn(clientContextMock); + when(ctxMock.getSessionReportTimeout()).thenReturn(1L); + when(ctxMock.getScheduler()).thenReturn(schedulerComponentMock); + + coapTransportResource = new CoapTransportResource(ctxMock, coapServerServiceMock, V1); + } + + @AfterEach + void tearDown() { + } + + // accessToken based tests + + @Test + void givenPostTelemetryAccessTokenRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { + // GIVEN + var request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), TELEMETRY); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenPostAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenGetAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + Request request = toGetAttributesAccessTokenRequest(StringUtils.randomAlphanumeric(20)); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForAttributesUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForRpcUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), RPC); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenRpcResponseAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toRpcResponseAccessTokenRequest(StringUtils.randomAlphanumeric(20), RANDOM.nextInt(100)); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClientSideRpcAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), RPC); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClaimingAccessTokenRequest_whenGetFeatureType_thenFeatureTypeClaim() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), CLAIM); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); + } + + // certificate based tests + + @Test + void givenPostTelemetryCertificateRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.POST, TELEMETRY); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenPostAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.POST, ATTRIBUTES); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenGetAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + var request = toGetAttributesCertificateRequest(); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForAttributesUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.GET, ATTRIBUTES); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForRpcUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.GET, RPC); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenRpcResponseCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toRpcResponseCertificateRequest(RANDOM.nextInt(100)); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClientSideRpcCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toCertificateRequest(CoAP.Code.POST, RPC); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClaimingCertificateRequest_whenGetFeatureType_thenFeatureTypeClaim() { + // GIVEN + Request request = toCertificateRequest(CoAP.Code.POST, CLAIM); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); + } + + // provision request + + @Test + void givenProvisionRequest_whenGetFeatureType_thenFeatureTypeProvision() { + // GIVEN + Request request = toCertificateRequest(CoAP.Code.POST, PROVISION); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.PROVISION, featureTypeOptional.get(), "Feature type is invalid"); + } + + private Request toAccessTokenRequest(CoAP.Code method, String accessToken, String featureType) { + return getAccessTokenRequest(method, accessToken, featureType, null, null); + } + + private Request toGetAttributesAccessTokenRequest(String accessToken) { + return getAccessTokenRequest(CoAP.Code.GET, accessToken, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); + } + + private Request toRpcResponseAccessTokenRequest(String accessToken, Integer requestId) { + return getAccessTokenRequest(CoAP.Code.POST, accessToken, CoapTransportResourceTest.RPC, requestId, null); + } + + private Request toCertificateRequest(CoAP.Code method, String featureType) { + return getCertificateRequest(method, featureType, null, null); + } + + private Request toGetAttributesCertificateRequest() { + return getCertificateRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); + } + + private Request toRpcResponseCertificateRequest(Integer requestId) { + return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, requestId, null); + } + + private Request getAccessTokenRequest(CoAP.Code method, String accessToken, String featureType, Integer requestId, String uriQuery) { + var request = new Request(method); + var options = new OptionSet(); + options.addUriPath(API); + options.addUriPath(V1); + options.addUriPath(accessToken); + options.addUriPath(featureType); + if (requestId != null) { + options.addUriPath(String.valueOf(requestId)); + } + if (uriQuery != null) { + options.setUriQuery(uriQuery); + } + request.setOptions(options); + return request; + } + + private Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + var request = new Request(method); + var options = new OptionSet(); + options.addUriPath(API); + options.addUriPath(V1); + options.addUriPath(featureType); + if (requestId != null) { + options.addUriPath(String.valueOf(requestId)); + } + if (uriQuery != null) { + options.setUriQuery(uriQuery); + } + request.setOptions(options); + return request; + } + + +} From f647fca59c61130aa5f16d2691d404c10aa5be19 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Sat, 12 Aug 2023 09:38:49 +0300 Subject: [PATCH 161/166] refactoring of test base --- .../coap/CoapTransportResourceTest.java | 71 ++++++++----------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java index 666f6c95df..2e5b367e4c 100644 --- a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java +++ b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java @@ -18,7 +18,9 @@ package org.thingsboard.server.transport.coap; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.OptionSet; import org.eclipse.californium.core.coap.Request; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.thingsboard.server.coapserver.CoapServerService; @@ -48,10 +50,10 @@ class CoapTransportResourceTest { private static final Random RANDOM = new Random(); - private CoapTransportResource coapTransportResource; + private static CoapTransportResource coapTransportResource; - @BeforeEach - void setUp() { + @BeforeAll + static void setUp() { var ctxMock = mock(CoapTransportContext.class); var coapServerServiceMock = mock(CoapServerService.class); @@ -67,16 +69,12 @@ class CoapTransportResourceTest { coapTransportResource = new CoapTransportResource(ctxMock, coapServerServiceMock, V1); } - @AfterEach - void tearDown() { - } - // accessToken based tests @Test void givenPostTelemetryAccessTokenRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { // GIVEN - var request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), TELEMETRY); + var request = toAccessTokenRequest(CoAP.Code.POST, TELEMETRY); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -89,7 +87,7 @@ class CoapTransportResourceTest { @Test void givenPostAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + Request request = toAccessTokenRequest(CoAP.Code.POST, ATTRIBUTES); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -102,7 +100,7 @@ class CoapTransportResourceTest { @Test void givenGetAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { // GIVEN - Request request = toGetAttributesAccessTokenRequest(StringUtils.randomAlphanumeric(20)); + Request request = toGetAttributesAccessTokenRequest(); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -114,7 +112,7 @@ class CoapTransportResourceTest { @Test void givenSubscribeForAttributesUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + Request request = toAccessTokenRequest(CoAP.Code.GET, ATTRIBUTES); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -126,7 +124,7 @@ class CoapTransportResourceTest { @Test void givenSubscribeForRpcUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), RPC); + Request request = toAccessTokenRequest(CoAP.Code.GET, RPC); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -138,7 +136,7 @@ class CoapTransportResourceTest { @Test void givenRpcResponseAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toRpcResponseAccessTokenRequest(StringUtils.randomAlphanumeric(20), RANDOM.nextInt(100)); + Request request = toRpcResponseAccessTokenRequest(); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -150,7 +148,7 @@ class CoapTransportResourceTest { @Test void givenClientSideRpcAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), RPC); + Request request = toAccessTokenRequest(CoAP.Code.POST, RPC); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -162,7 +160,7 @@ class CoapTransportResourceTest { @Test void givenClaimingAccessTokenRequest_whenGetFeatureType_thenFeatureTypeClaim() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), CLAIM); + Request request = toAccessTokenRequest(CoAP.Code.POST, CLAIM); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -241,7 +239,7 @@ class CoapTransportResourceTest { @Test void givenRpcResponseCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toRpcResponseCertificateRequest(RANDOM.nextInt(100)); + Request request = toRpcResponseCertificateRequest(); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -291,16 +289,16 @@ class CoapTransportResourceTest { assertEquals(FeatureType.PROVISION, featureTypeOptional.get(), "Feature type is invalid"); } - private Request toAccessTokenRequest(CoAP.Code method, String accessToken, String featureType) { - return getAccessTokenRequest(method, accessToken, featureType, null, null); + private Request toAccessTokenRequest(CoAP.Code method, String featureType) { + return getAccessTokenRequest(method, featureType, null, null); } - private Request toGetAttributesAccessTokenRequest(String accessToken) { - return getAccessTokenRequest(CoAP.Code.GET, accessToken, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); + private Request toGetAttributesAccessTokenRequest() { + return getAccessTokenRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseAccessTokenRequest(String accessToken, Integer requestId) { - return getAccessTokenRequest(CoAP.Code.POST, accessToken, CoapTransportResourceTest.RPC, requestId, null); + private Request toRpcResponseAccessTokenRequest() { + return getAccessTokenRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } private Request toCertificateRequest(CoAP.Code method, String featureType) { @@ -311,32 +309,26 @@ class CoapTransportResourceTest { return getCertificateRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseCertificateRequest(Integer requestId) { - return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, requestId, null); + private Request toRpcResponseCertificateRequest() { + return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } - private Request getAccessTokenRequest(CoAP.Code method, String accessToken, String featureType, Integer requestId, String uriQuery) { - var request = new Request(method); - var options = new OptionSet(); - options.addUriPath(API); - options.addUriPath(V1); - options.addUriPath(accessToken); - options.addUriPath(featureType); - if (requestId != null) { - options.addUriPath(String.valueOf(requestId)); - } - if (uriQuery != null) { - options.setUriQuery(uriQuery); - } - request.setOptions(options); - return request; + private Request getAccessTokenRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + return getRequest(method, featureType, false, requestId, uriQuery); } private Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + return getRequest(method, featureType, true, requestId, uriQuery); + } + + private Request getRequest(CoAP.Code method, String featureType, boolean dtls, Integer requestId, String uriQuery) { var request = new Request(method); var options = new OptionSet(); options.addUriPath(API); options.addUriPath(V1); + if (!dtls) { + options.addUriPath(StringUtils.randomAlphanumeric(20)); + } options.addUriPath(featureType); if (requestId != null) { options.addUriPath(String.valueOf(requestId)); @@ -348,5 +340,4 @@ class CoapTransportResourceTest { return request; } - } From c5ff8b4229af3adfde1e5c8436540afca1d71512 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Sat, 12 Aug 2023 09:52:36 +0300 Subject: [PATCH 162/166] refactored to parameterized test --- .../coap/CoapTransportResourceTest.java | 270 +++--------------- 1 file changed, 44 insertions(+), 226 deletions(-) diff --git a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java index 2e5b367e4c..c7f33e3694 100644 --- a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java +++ b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java @@ -18,11 +18,10 @@ package org.thingsboard.server.transport.coap; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.OptionSet; import org.eclipse.californium.core.coap.Request; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.thingsboard.server.coapserver.CoapServerService; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.msg.session.FeatureType; @@ -31,6 +30,7 @@ import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.transport.coap.client.CoapClientContext; import java.util.Random; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -69,259 +69,77 @@ class CoapTransportResourceTest { coapTransportResource = new CoapTransportResource(ctxMock, coapServerServiceMock, V1); } - // accessToken based tests - - @Test - void givenPostTelemetryAccessTokenRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { - // GIVEN - var request = toAccessTokenRequest(CoAP.Code.POST, TELEMETRY); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenPostAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, ATTRIBUTES); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenGetAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - Request request = toGetAttributesAccessTokenRequest(); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForAttributesUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, ATTRIBUTES); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForRpcUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, RPC); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenRpcResponseAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toRpcResponseAccessTokenRequest(); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenClientSideRpcAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, RPC); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenClaimingAccessTokenRequest_whenGetFeatureType_thenFeatureTypeClaim() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, CLAIM); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); - } - - // certificate based tests - - @Test - void givenPostTelemetryCertificateRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.POST, TELEMETRY); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenPostAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.POST, ATTRIBUTES); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenGetAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - var request = toGetAttributesCertificateRequest(); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForAttributesUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.GET, ATTRIBUTES); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForRpcUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.GET, RPC); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenRpcResponseCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toRpcResponseCertificateRequest(); - - // WHEN + @ParameterizedTest + @MethodSource("provideRequestAndFeatureType") + void givenRequest_whenGetFeatureType_thenReturnedExpectedFeatureType(Request request, FeatureType expectedFeatureType) { var featureTypeOptional = coapTransportResource.getFeatureType(request); - // THEN assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + assertEquals(expectedFeatureType, featureTypeOptional.get(), "Feature type is invalid"); } - @Test - void givenClientSideRpcCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toCertificateRequest(CoAP.Code.POST, RPC); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + static Stream provideRequestAndFeatureType() { + return Stream.of( + // accessToken based tests + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, TELEMETRY), FeatureType.TELEMETRY), + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toGetAttributesAccessTokenRequest(), FeatureType.ATTRIBUTES), + Arguments.of(toAccessTokenRequest(CoAP.Code.GET, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toAccessTokenRequest(CoAP.Code.GET, RPC), FeatureType.RPC), + Arguments.of(toRpcResponseAccessTokenRequest(), FeatureType.RPC), + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, RPC), FeatureType.RPC), + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, CLAIM), FeatureType.CLAIM), + // certificate based tests + Arguments.of(toCertificateRequest(CoAP.Code.POST, TELEMETRY), FeatureType.TELEMETRY), + Arguments.of(toCertificateRequest(CoAP.Code.POST, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toGetAttributesCertificateRequest(), FeatureType.ATTRIBUTES), + Arguments.of(toCertificateRequest(CoAP.Code.GET, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toCertificateRequest(CoAP.Code.GET, RPC), FeatureType.RPC), + Arguments.of(toRpcResponseCertificateRequest(), FeatureType.RPC), + Arguments.of(toCertificateRequest(CoAP.Code.POST, RPC), FeatureType.RPC), + Arguments.of(toCertificateRequest(CoAP.Code.POST, CLAIM), FeatureType.CLAIM), + // provision request + Arguments.of(toProvisionRequest(), FeatureType.PROVISION) + ); } - @Test - void givenClaimingCertificateRequest_whenGetFeatureType_thenFeatureTypeClaim() { - // GIVEN - Request request = toCertificateRequest(CoAP.Code.POST, CLAIM); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); - } - - // provision request - - @Test - void givenProvisionRequest_whenGetFeatureType_thenFeatureTypeProvision() { - // GIVEN - Request request = toCertificateRequest(CoAP.Code.POST, PROVISION); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.PROVISION, featureTypeOptional.get(), "Feature type is invalid"); - } - - private Request toAccessTokenRequest(CoAP.Code method, String featureType) { + private static Request toAccessTokenRequest(CoAP.Code method, String featureType) { return getAccessTokenRequest(method, featureType, null, null); } - private Request toGetAttributesAccessTokenRequest() { + private static Request toGetAttributesAccessTokenRequest() { return getAccessTokenRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseAccessTokenRequest() { + private static Request toRpcResponseAccessTokenRequest() { return getAccessTokenRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } - private Request toCertificateRequest(CoAP.Code method, String featureType) { + private static Request toCertificateRequest(CoAP.Code method, String featureType) { return getCertificateRequest(method, featureType, null, null); } - private Request toGetAttributesCertificateRequest() { + private static Request toGetAttributesCertificateRequest() { return getCertificateRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseCertificateRequest() { + private static Request toRpcResponseCertificateRequest() { return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } - private Request getAccessTokenRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + private static Request getAccessTokenRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { return getRequest(method, featureType, false, requestId, uriQuery); } - private Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + private static Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { return getRequest(method, featureType, true, requestId, uriQuery); } - private Request getRequest(CoAP.Code method, String featureType, boolean dtls, Integer requestId, String uriQuery) { + private static Request toProvisionRequest() { + return getRequest(CoAP.Code.POST, PROVISION, true, null, null); + } + + private static Request getRequest(CoAP.Code method, String featureType, boolean dtls, Integer requestId, String uriQuery) { var request = new Request(method); var options = new OptionSet(); options.addUriPath(API); From 928962898e266cfabebd949bd2e1b7f106b84769 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 11 Aug 2023 19:23:51 +0300 Subject: [PATCH 163/166] PROD-2339: fix getFeatureType method to handle RPC server-side response over DTLS --- .../transport/coap/CoapTransportResource.java | 11 +- .../coap/CoapTransportResourceTest.java | 352 ++++++++++++++++++ 2 files changed, 360 insertions(+), 3 deletions(-) create mode 100644 common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 7dde25bfd0..263df9e7a2 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -30,6 +30,7 @@ import org.thingsboard.server.coapserver.TbCoapDtlsSessionInfo; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; @@ -380,12 +381,16 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } - private Optional getFeatureType(Request request) { + protected Optional getFeatureType(Request request) { List uriPath = request.getOptions().getUriPath(); try { - if (uriPath.size() >= FEATURE_TYPE_POSITION) { + int size = uriPath.size(); + if (size >= FEATURE_TYPE_POSITION) { + if (size == FEATURE_TYPE_POSITION && StringUtils.isNumeric(uriPath.get(size - 1))) { + return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 2).toUpperCase())); + } return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); - } else if (uriPath.size() >= FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST) { + } else if (size == FEATURE_TYPE_POSITION_CERTIFICATE_REQUEST) { if (uriPath.contains(DataConstants.PROVISION)) { return Optional.of(FeatureType.valueOf(DataConstants.PROVISION.toUpperCase())); } diff --git a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java new file mode 100644 index 0000000000..666f6c95df --- /dev/null +++ b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java @@ -0,0 +1,352 @@ +/** + * 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.transport.coap; + +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.OptionSet; +import org.eclipse.californium.core.coap.Request; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.thingsboard.server.coapserver.CoapServerService; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; +import org.thingsboard.server.transport.coap.client.CoapClientContext; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class CoapTransportResourceTest { + + private static final String V1 = "v1"; + private static final String API = "api"; + private static final String TELEMETRY = "telemetry"; + private static final String ATTRIBUTES = "attributes"; + private static final String RPC = "rpc"; + private static final String CLAIM = "claim"; + private static final String PROVISION = "provision"; + private static final String GET_ATTRIBUTES_URI_QUERY = "clientKeys=attribute1,attribute2&sharedKeys=shared1,shared2"; + + private static final Random RANDOM = new Random(); + + private CoapTransportResource coapTransportResource; + + @BeforeEach + void setUp() { + + var ctxMock = mock(CoapTransportContext.class); + var coapServerServiceMock = mock(CoapServerService.class); + var transportServiceMock = mock(TransportService.class); + var clientContextMock = mock(CoapClientContext.class); + var schedulerComponentMock = mock(SchedulerComponent.class); + + when(ctxMock.getTransportService()).thenReturn(transportServiceMock); + when(ctxMock.getClientContext()).thenReturn(clientContextMock); + when(ctxMock.getSessionReportTimeout()).thenReturn(1L); + when(ctxMock.getScheduler()).thenReturn(schedulerComponentMock); + + coapTransportResource = new CoapTransportResource(ctxMock, coapServerServiceMock, V1); + } + + @AfterEach + void tearDown() { + } + + // accessToken based tests + + @Test + void givenPostTelemetryAccessTokenRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { + // GIVEN + var request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), TELEMETRY); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenPostAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenGetAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + Request request = toGetAttributesAccessTokenRequest(StringUtils.randomAlphanumeric(20)); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForAttributesUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForRpcUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), RPC); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenRpcResponseAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toRpcResponseAccessTokenRequest(StringUtils.randomAlphanumeric(20), RANDOM.nextInt(100)); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClientSideRpcAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), RPC); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClaimingAccessTokenRequest_whenGetFeatureType_thenFeatureTypeClaim() { + // GIVEN + Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), CLAIM); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); + } + + // certificate based tests + + @Test + void givenPostTelemetryCertificateRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.POST, TELEMETRY); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenPostAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.POST, ATTRIBUTES); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenGetAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + var request = toGetAttributesCertificateRequest(); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForAttributesUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.GET, ATTRIBUTES); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenSubscribeForRpcUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + var request = toCertificateRequest(CoAP.Code.GET, RPC); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenRpcResponseCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toRpcResponseCertificateRequest(RANDOM.nextInt(100)); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClientSideRpcCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { + // GIVEN + Request request = toCertificateRequest(CoAP.Code.POST, RPC); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + } + + @Test + void givenClaimingCertificateRequest_whenGetFeatureType_thenFeatureTypeClaim() { + // GIVEN + Request request = toCertificateRequest(CoAP.Code.POST, CLAIM); + + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); + } + + // provision request + + @Test + void givenProvisionRequest_whenGetFeatureType_thenFeatureTypeProvision() { + // GIVEN + Request request = toCertificateRequest(CoAP.Code.POST, PROVISION); + // WHEN + var featureTypeOptional = coapTransportResource.getFeatureType(request); + + // THEN + assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); + assertEquals(FeatureType.PROVISION, featureTypeOptional.get(), "Feature type is invalid"); + } + + private Request toAccessTokenRequest(CoAP.Code method, String accessToken, String featureType) { + return getAccessTokenRequest(method, accessToken, featureType, null, null); + } + + private Request toGetAttributesAccessTokenRequest(String accessToken) { + return getAccessTokenRequest(CoAP.Code.GET, accessToken, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); + } + + private Request toRpcResponseAccessTokenRequest(String accessToken, Integer requestId) { + return getAccessTokenRequest(CoAP.Code.POST, accessToken, CoapTransportResourceTest.RPC, requestId, null); + } + + private Request toCertificateRequest(CoAP.Code method, String featureType) { + return getCertificateRequest(method, featureType, null, null); + } + + private Request toGetAttributesCertificateRequest() { + return getCertificateRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); + } + + private Request toRpcResponseCertificateRequest(Integer requestId) { + return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, requestId, null); + } + + private Request getAccessTokenRequest(CoAP.Code method, String accessToken, String featureType, Integer requestId, String uriQuery) { + var request = new Request(method); + var options = new OptionSet(); + options.addUriPath(API); + options.addUriPath(V1); + options.addUriPath(accessToken); + options.addUriPath(featureType); + if (requestId != null) { + options.addUriPath(String.valueOf(requestId)); + } + if (uriQuery != null) { + options.setUriQuery(uriQuery); + } + request.setOptions(options); + return request; + } + + private Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + var request = new Request(method); + var options = new OptionSet(); + options.addUriPath(API); + options.addUriPath(V1); + options.addUriPath(featureType); + if (requestId != null) { + options.addUriPath(String.valueOf(requestId)); + } + if (uriQuery != null) { + options.setUriQuery(uriQuery); + } + request.setOptions(options); + return request; + } + + +} From 14ad1df873200ef8d69b82f98bab9cd6f8416d39 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Sat, 12 Aug 2023 09:38:49 +0300 Subject: [PATCH 164/166] refactoring of test base --- .../coap/CoapTransportResourceTest.java | 71 ++++++++----------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java index 666f6c95df..2e5b367e4c 100644 --- a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java +++ b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java @@ -18,7 +18,9 @@ package org.thingsboard.server.transport.coap; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.OptionSet; import org.eclipse.californium.core.coap.Request; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.thingsboard.server.coapserver.CoapServerService; @@ -48,10 +50,10 @@ class CoapTransportResourceTest { private static final Random RANDOM = new Random(); - private CoapTransportResource coapTransportResource; + private static CoapTransportResource coapTransportResource; - @BeforeEach - void setUp() { + @BeforeAll + static void setUp() { var ctxMock = mock(CoapTransportContext.class); var coapServerServiceMock = mock(CoapServerService.class); @@ -67,16 +69,12 @@ class CoapTransportResourceTest { coapTransportResource = new CoapTransportResource(ctxMock, coapServerServiceMock, V1); } - @AfterEach - void tearDown() { - } - // accessToken based tests @Test void givenPostTelemetryAccessTokenRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { // GIVEN - var request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), TELEMETRY); + var request = toAccessTokenRequest(CoAP.Code.POST, TELEMETRY); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -89,7 +87,7 @@ class CoapTransportResourceTest { @Test void givenPostAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + Request request = toAccessTokenRequest(CoAP.Code.POST, ATTRIBUTES); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -102,7 +100,7 @@ class CoapTransportResourceTest { @Test void givenGetAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { // GIVEN - Request request = toGetAttributesAccessTokenRequest(StringUtils.randomAlphanumeric(20)); + Request request = toGetAttributesAccessTokenRequest(); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -114,7 +112,7 @@ class CoapTransportResourceTest { @Test void givenSubscribeForAttributesUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), ATTRIBUTES); + Request request = toAccessTokenRequest(CoAP.Code.GET, ATTRIBUTES); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -126,7 +124,7 @@ class CoapTransportResourceTest { @Test void givenSubscribeForRpcUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, StringUtils.randomAlphanumeric(20), RPC); + Request request = toAccessTokenRequest(CoAP.Code.GET, RPC); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -138,7 +136,7 @@ class CoapTransportResourceTest { @Test void givenRpcResponseAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toRpcResponseAccessTokenRequest(StringUtils.randomAlphanumeric(20), RANDOM.nextInt(100)); + Request request = toRpcResponseAccessTokenRequest(); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -150,7 +148,7 @@ class CoapTransportResourceTest { @Test void givenClientSideRpcAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), RPC); + Request request = toAccessTokenRequest(CoAP.Code.POST, RPC); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -162,7 +160,7 @@ class CoapTransportResourceTest { @Test void givenClaimingAccessTokenRequest_whenGetFeatureType_thenFeatureTypeClaim() { // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, StringUtils.randomAlphanumeric(20), CLAIM); + Request request = toAccessTokenRequest(CoAP.Code.POST, CLAIM); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -241,7 +239,7 @@ class CoapTransportResourceTest { @Test void givenRpcResponseCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { // GIVEN - Request request = toRpcResponseCertificateRequest(RANDOM.nextInt(100)); + Request request = toRpcResponseCertificateRequest(); // WHEN var featureTypeOptional = coapTransportResource.getFeatureType(request); @@ -291,16 +289,16 @@ class CoapTransportResourceTest { assertEquals(FeatureType.PROVISION, featureTypeOptional.get(), "Feature type is invalid"); } - private Request toAccessTokenRequest(CoAP.Code method, String accessToken, String featureType) { - return getAccessTokenRequest(method, accessToken, featureType, null, null); + private Request toAccessTokenRequest(CoAP.Code method, String featureType) { + return getAccessTokenRequest(method, featureType, null, null); } - private Request toGetAttributesAccessTokenRequest(String accessToken) { - return getAccessTokenRequest(CoAP.Code.GET, accessToken, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); + private Request toGetAttributesAccessTokenRequest() { + return getAccessTokenRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseAccessTokenRequest(String accessToken, Integer requestId) { - return getAccessTokenRequest(CoAP.Code.POST, accessToken, CoapTransportResourceTest.RPC, requestId, null); + private Request toRpcResponseAccessTokenRequest() { + return getAccessTokenRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } private Request toCertificateRequest(CoAP.Code method, String featureType) { @@ -311,32 +309,26 @@ class CoapTransportResourceTest { return getCertificateRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseCertificateRequest(Integer requestId) { - return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, requestId, null); + private Request toRpcResponseCertificateRequest() { + return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } - private Request getAccessTokenRequest(CoAP.Code method, String accessToken, String featureType, Integer requestId, String uriQuery) { - var request = new Request(method); - var options = new OptionSet(); - options.addUriPath(API); - options.addUriPath(V1); - options.addUriPath(accessToken); - options.addUriPath(featureType); - if (requestId != null) { - options.addUriPath(String.valueOf(requestId)); - } - if (uriQuery != null) { - options.setUriQuery(uriQuery); - } - request.setOptions(options); - return request; + private Request getAccessTokenRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + return getRequest(method, featureType, false, requestId, uriQuery); } private Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + return getRequest(method, featureType, true, requestId, uriQuery); + } + + private Request getRequest(CoAP.Code method, String featureType, boolean dtls, Integer requestId, String uriQuery) { var request = new Request(method); var options = new OptionSet(); options.addUriPath(API); options.addUriPath(V1); + if (!dtls) { + options.addUriPath(StringUtils.randomAlphanumeric(20)); + } options.addUriPath(featureType); if (requestId != null) { options.addUriPath(String.valueOf(requestId)); @@ -348,5 +340,4 @@ class CoapTransportResourceTest { return request; } - } From a90653d6606eee59345943e41d7ec2b27fceb266 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Sat, 12 Aug 2023 09:52:36 +0300 Subject: [PATCH 165/166] refactored to parameterized test --- .../coap/CoapTransportResourceTest.java | 270 +++--------------- 1 file changed, 44 insertions(+), 226 deletions(-) diff --git a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java index 2e5b367e4c..c7f33e3694 100644 --- a/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java +++ b/common/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapTransportResourceTest.java @@ -18,11 +18,10 @@ package org.thingsboard.server.transport.coap; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.OptionSet; import org.eclipse.californium.core.coap.Request; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.thingsboard.server.coapserver.CoapServerService; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.msg.session.FeatureType; @@ -31,6 +30,7 @@ import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.transport.coap.client.CoapClientContext; import java.util.Random; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -69,259 +69,77 @@ class CoapTransportResourceTest { coapTransportResource = new CoapTransportResource(ctxMock, coapServerServiceMock, V1); } - // accessToken based tests - - @Test - void givenPostTelemetryAccessTokenRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { - // GIVEN - var request = toAccessTokenRequest(CoAP.Code.POST, TELEMETRY); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenPostAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, ATTRIBUTES); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenGetAttributesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - Request request = toGetAttributesAccessTokenRequest(); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForAttributesUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, ATTRIBUTES); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForRpcUpdatesAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.GET, RPC); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenRpcResponseAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toRpcResponseAccessTokenRequest(); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenClientSideRpcAccessTokenRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, RPC); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenClaimingAccessTokenRequest_whenGetFeatureType_thenFeatureTypeClaim() { - // GIVEN - Request request = toAccessTokenRequest(CoAP.Code.POST, CLAIM); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); - } - - // certificate based tests - - @Test - void givenPostTelemetryCertificateRequest_whenGetFeatureType_thenFeatureTypeTelemetry() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.POST, TELEMETRY); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.TELEMETRY, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenPostAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.POST, ATTRIBUTES); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenGetAttributesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - var request = toGetAttributesCertificateRequest(); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForAttributesUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeAttributes() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.GET, ATTRIBUTES); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.ATTRIBUTES, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenSubscribeForRpcUpdatesCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - var request = toCertificateRequest(CoAP.Code.GET, RPC); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); - } - - @Test - void givenRpcResponseCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toRpcResponseCertificateRequest(); - - // WHEN + @ParameterizedTest + @MethodSource("provideRequestAndFeatureType") + void givenRequest_whenGetFeatureType_thenReturnedExpectedFeatureType(Request request, FeatureType expectedFeatureType) { var featureTypeOptional = coapTransportResource.getFeatureType(request); - // THEN assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + assertEquals(expectedFeatureType, featureTypeOptional.get(), "Feature type is invalid"); } - @Test - void givenClientSideRpcCertificateRequest_whenGetFeatureType_thenFeatureTypeRpc() { - // GIVEN - Request request = toCertificateRequest(CoAP.Code.POST, RPC); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.RPC, featureTypeOptional.get(), "Feature type is invalid"); + static Stream provideRequestAndFeatureType() { + return Stream.of( + // accessToken based tests + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, TELEMETRY), FeatureType.TELEMETRY), + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toGetAttributesAccessTokenRequest(), FeatureType.ATTRIBUTES), + Arguments.of(toAccessTokenRequest(CoAP.Code.GET, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toAccessTokenRequest(CoAP.Code.GET, RPC), FeatureType.RPC), + Arguments.of(toRpcResponseAccessTokenRequest(), FeatureType.RPC), + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, RPC), FeatureType.RPC), + Arguments.of(toAccessTokenRequest(CoAP.Code.POST, CLAIM), FeatureType.CLAIM), + // certificate based tests + Arguments.of(toCertificateRequest(CoAP.Code.POST, TELEMETRY), FeatureType.TELEMETRY), + Arguments.of(toCertificateRequest(CoAP.Code.POST, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toGetAttributesCertificateRequest(), FeatureType.ATTRIBUTES), + Arguments.of(toCertificateRequest(CoAP.Code.GET, ATTRIBUTES), FeatureType.ATTRIBUTES), + Arguments.of(toCertificateRequest(CoAP.Code.GET, RPC), FeatureType.RPC), + Arguments.of(toRpcResponseCertificateRequest(), FeatureType.RPC), + Arguments.of(toCertificateRequest(CoAP.Code.POST, RPC), FeatureType.RPC), + Arguments.of(toCertificateRequest(CoAP.Code.POST, CLAIM), FeatureType.CLAIM), + // provision request + Arguments.of(toProvisionRequest(), FeatureType.PROVISION) + ); } - @Test - void givenClaimingCertificateRequest_whenGetFeatureType_thenFeatureTypeClaim() { - // GIVEN - Request request = toCertificateRequest(CoAP.Code.POST, CLAIM); - - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.CLAIM, featureTypeOptional.get(), "Feature type is invalid"); - } - - // provision request - - @Test - void givenProvisionRequest_whenGetFeatureType_thenFeatureTypeProvision() { - // GIVEN - Request request = toCertificateRequest(CoAP.Code.POST, PROVISION); - // WHEN - var featureTypeOptional = coapTransportResource.getFeatureType(request); - - // THEN - assertTrue(featureTypeOptional.isPresent(), "Optional is empty"); - assertEquals(FeatureType.PROVISION, featureTypeOptional.get(), "Feature type is invalid"); - } - - private Request toAccessTokenRequest(CoAP.Code method, String featureType) { + private static Request toAccessTokenRequest(CoAP.Code method, String featureType) { return getAccessTokenRequest(method, featureType, null, null); } - private Request toGetAttributesAccessTokenRequest() { + private static Request toGetAttributesAccessTokenRequest() { return getAccessTokenRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseAccessTokenRequest() { + private static Request toRpcResponseAccessTokenRequest() { return getAccessTokenRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } - private Request toCertificateRequest(CoAP.Code method, String featureType) { + private static Request toCertificateRequest(CoAP.Code method, String featureType) { return getCertificateRequest(method, featureType, null, null); } - private Request toGetAttributesCertificateRequest() { + private static Request toGetAttributesCertificateRequest() { return getCertificateRequest(CoAP.Code.GET, CoapTransportResourceTest.ATTRIBUTES, null, CoapTransportResourceTest.GET_ATTRIBUTES_URI_QUERY); } - private Request toRpcResponseCertificateRequest() { + private static Request toRpcResponseCertificateRequest() { return getCertificateRequest(CoAP.Code.POST, CoapTransportResourceTest.RPC, RANDOM.nextInt(100), null); } - private Request getAccessTokenRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + private static Request getAccessTokenRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { return getRequest(method, featureType, false, requestId, uriQuery); } - private Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { + private static Request getCertificateRequest(CoAP.Code method, String featureType, Integer requestId, String uriQuery) { return getRequest(method, featureType, true, requestId, uriQuery); } - private Request getRequest(CoAP.Code method, String featureType, boolean dtls, Integer requestId, String uriQuery) { + private static Request toProvisionRequest() { + return getRequest(CoAP.Code.POST, PROVISION, true, null, null); + } + + private static Request getRequest(CoAP.Code method, String featureType, boolean dtls, Integer requestId, String uriQuery) { var request = new Request(method); var options = new OptionSet(); options.addUriPath(API); From 5f39e743ec338e1925c3a9926c4cd2d9a15421c4 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 14 Aug 2023 16:35:42 +0300 Subject: [PATCH 166/166] Update form.scss --- ui-ngx/src/form.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 79f141e9d5..8bf8aef2ab 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -380,9 +380,6 @@ gap: 12px; padding-left: 12px; padding-right: 12px; - &.no-padding-right { - padding-right: 0; - } } &-cell { font-weight: 400;
+
-
device.connectivity.use-following-instructions
-
- device.connectivity.install-curl - -
-
-
device.connectivity.http-command
- -
-
-
device.connectivity.https-command
- -
+
device.connectivity.use-following-instructions
+ + + + + Windows + + +
+
+
device.connectivity.install-necessary-client-tools
+
device.connectivity.install-curl-windows
+
+ + +
+
+
+ + + + MacOS + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ + +
+
+
+ + + + Linux + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ + +
+
+
+
-
-
device.connectivity.use-following-instructions
-
- device.connectivity.install-mqtt-client - -
-
-
-
device.connectivity.mqtt-command
- -
-
+
device.connectivity.use-following-instructions
+ + + + + Windows + + +
+
+
device.connectivity.install-necessary-client-tools
+
Coming Soon!!!!
+
+ + +
+
+
+ + + + MacOS + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ + +
+
+
+ + + + Linux + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ + +
+
+
+ + + + Docker + + +
+ + +
+
+
+
-
-
device.connectivity.use-following-instructions
-
- device.connectivity.install-coap-cli - -
-
-
-
device.connectivity.coap-command
- -
-
-
-
device.connectivity.coaps-command
- -
- -
device.connectivity.coaps-x509-command
- -
-
+
device.connectivity.use-following-instructions
+ + + + + MacOS + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ + +
+
+
+ + + + Linux + + +
+
+
device.connectivity.install-necessary-client-tools
+ +
+ + +
+
+
+ + + + Docker + + +
+ + +
+
+
+
-
device.connectivity.snmp-command
-
- - - {{ 'action.see-documentation' | translate }} - open_in_new - - +
+ +
-
device.connectivity.lwm2m-command
-
- - - {{ 'action.see-documentation' | translate }} - open_in_new - - +
+ +
@@ -224,3 +338,44 @@
attribute.no-latest-telemetry
+ + +
+
+
device.connectivity.execute-following-command
+ + {{ cmd.noSecLabel }} + {{ cmd.secLabel }} + +
+ + + + + +
+ +
+ + + + +
+
+
+
+ + + + diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss index e7c88bb2cb..1a95da0a14 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.scss @@ -68,6 +68,10 @@ font-size: 14px; } + .tb-flex-1 { + flex: 1; + } + .tb-form-table-body { max-height: 88px; overflow-y: auto; @@ -84,6 +88,10 @@ } } + .tb-install-windows { + min-height: 42px; + } + @media #{$mat-sm} { width: 470px; } @@ -112,12 +120,13 @@ .code-wrapper { padding: 0; pre[class*=language-] { + margin: 0; background: #F3F6FA; border-color: #305680; } } button.clipboard-btn { - right: 0; + right: -2px; p { color: #305680; } @@ -148,4 +157,12 @@ box-sizing: initial; } } + + .tabs-icon { + margin-right: 8px; + } + + .tb-form-panel.tb-tab-body { + padding: 16px 0 0; + } } diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts index 8a427512aa..f185d88c6a 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts @@ -38,16 +38,19 @@ import { BasicTransportType, DeviceTransportType, deviceTransportTypeTranslationMap, - NetworkTransportType + NetworkTransportType, + PublishTelemetryCommand } from '@shared/models/device.models'; import { UserSettingsService } from '@core/http/user-settings.service'; import { ActionPreferencesUpdateUserSettings } from '@core/auth/auth.actions'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { getOS } from '@core/utils'; export interface DeviceCheckConnectivityDialogData { deviceId: EntityId; afterAdd: boolean; } + @Component({ selector: 'tb-device-check-connectivity-dialog', templateUrl: './device-check-connectivity-dialog.component.html', @@ -62,7 +65,7 @@ export class DeviceCheckConnectivityDialogComponent extends latestTelemetry: Array = []; - commands: {[key: string]: string}; + commands: PublishTelemetryCommand; allowTransportType = new Set(); selectTransportType: NetworkTransportType; @@ -77,6 +80,45 @@ export class DeviceCheckConnectivityDialogComponent extends notShowAgain = false; + httpTabIndex = 0; + mqttTabIndex = 0; + coapTabIndex = 0; + + readonly installCoap = '```bash\n' + + 'git clone https://github.com/obgm/libcoap --recursive\n' + + '{:copy-code}\n' + + '```\n' + + '
\n' + + '\n' + + '```bash\n' + + 'cd libcoap\n' + + '{:copy-code}\n' + + '```\n' + + '
\n' + + '\n' + + '```bash\n' + + './autogen.sh\n' + + '{:copy-code}\n' + + '```\n' + + '
\n' + + '\n' + + '```bash\n' + + './configure --with-openssl --disable-doxygen --disable-manpages --disable-shared\n' + + '{:copy-code}\n' + + '```\n' + + '
\n' + + '\n' + + '```bash\n' + + 'make\n' + + '{:copy-code}\n' + + '```\n' + + '
\n' + + '\n' + + '```bash\n' + + 'sudo make install\n' + + '{:copy-code}\n' + + '```'; + private telemetrySubscriber: TelemetrySubscriber; private currentTime = Date.now(); @@ -125,11 +167,21 @@ export class DeviceCheckConnectivityDialogComponent extends } } - createMarkDownCommand(command: string): string { + createMarkDownCommand(commands: string | string[]): string { + if (Array.isArray(commands)) { + const formatCommands: Array = []; + commands.forEach(command => formatCommands.push(this.createMarkDownSingleCommand(command))); + return formatCommands.join('
\n'); + } else { + return this.createMarkDownSingleCommand(commands); + } + } + + private createMarkDownSingleCommand(command: string): string { return '```bash\n' + - command + - '{:copy-code}\n' + - '```'; + command + + '{:copy-code}\n' + + '```'; } private loadCommands() { @@ -144,6 +196,7 @@ export class DeviceCheckConnectivityDialogComponent extends } }); this.selectTransportType = this.allowTransportType.values().next().value; + this.selectTabIndexForUserOS(); this.loadedCommand = true; } ); @@ -180,4 +233,28 @@ export class DeviceCheckConnectivityDialogComponent extends }); } + private selectTabIndexForUserOS() { + const currentOS = getOS(); + switch (currentOS) { + case 'linux': + case 'android': + this.httpTabIndex = 2; + this.mqttTabIndex = 2; + this.coapTabIndex = 1; + break; + case 'macos': + case 'ios': + this.httpTabIndex = 1; + this.mqttTabIndex = 1; + break; + case 'windows': + this.httpTabIndex = 0; + this.mqttTabIndex = 0; + break; + default: + this.mqttTabIndex = this.commands.mqtt?.docker ? 3 : 0; + this.coapTabIndex = this.commands.coap?.docker ? 2 : 1; + } + } + } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index b371c131df..e5dd1e9efc 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -837,6 +837,31 @@ export interface ClaimResult { response: ClaimResponse; } +export interface PublishTelemetryCommand { + http?: { + http?: string; + https?: string; + }; + mqtt: { + mqtt?: string; + mqtts?: string | Array; + docker?: { + mqtt?: string; + mqtts?: string | Array; + }; + }; + coap: { + coap?: string; + coaps?: string | Array; + docker?: { + coap?: string; + coaps?: string | Array; + }; + }; + lwm2m?: string; + snmp?: string; +} + export const dayOfWeekTranslations = new Array( 'device-profile.schedule-day.monday', 'device-profile.schedule-day.tuesday', diff --git a/ui-ngx/src/assets/docker.svg b/ui-ngx/src/assets/docker.svg new file mode 100644 index 0000000000..f152739de6 --- /dev/null +++ b/ui-ngx/src/assets/docker.svg @@ -0,0 +1 @@ + diff --git a/ui-ngx/src/assets/help/en_US/device/install_coap_client.md b/ui-ngx/src/assets/help/en_US/device/install_coap_client.md deleted file mode 100644 index 0612acad26..0000000000 --- a/ui-ngx/src/assets/help/en_US/device/install_coap_client.md +++ /dev/null @@ -1,40 +0,0 @@ - #### CoAP installation instructions ---- -
- -Install coap client tool on your **Linux/macOS**: - -```bash -git clone https://github.com/obgm/libcoap --recursive -{:copy-code} -``` -
- -```bash -cd libcoap -{:copy-code} -``` -
- -```bash -./autogen.sh -{:copy-code} -``` -
- -```bash -./configure --with-openssl --disable-doxygen --disable-manpages --disable-shared -{:copy-code} -``` -
- -```bash -make -{:copy-code} -``` -
- -```bash -sudo make install -{:copy-code} -``` diff --git a/ui-ngx/src/assets/help/en_US/device/install_curl.md b/ui-ngx/src/assets/help/en_US/device/install_curl.md deleted file mode 100644 index 0ba60fc590..0000000000 --- a/ui-ngx/src/assets/help/en_US/device/install_curl.md +++ /dev/null @@ -1,34 +0,0 @@ -#### cURL installation instructions ---- -
-
- - Ubuntu - MacOS - Windows - -
- - -

Install cURL tool:

- -
- -

Install cURL tool:

- -
- -
Starting Windows 10 b17063, cURL is available by default.
-
-
-
diff --git a/ui-ngx/src/assets/help/en_US/device/install_mqtt_client.md b/ui-ngx/src/assets/help/en_US/device/install_mqtt_client.md deleted file mode 100644 index 941dce7ab4..0000000000 --- a/ui-ngx/src/assets/help/en_US/device/install_mqtt_client.md +++ /dev/null @@ -1,38 +0,0 @@ - #### MQTT client tool installation instructions ---- -
-
- - Ubuntu - MacOS - Windows - -
- - -

Install mqtt client tool:

- -
- -

Install mqtt client tool:

- -
- -

Install mqtt client tool:

- - descriptionHow to install MQTT Box -
-
-
diff --git a/ui-ngx/src/assets/linux.svg b/ui-ngx/src/assets/linux.svg new file mode 100644 index 0000000000..66f505437f --- /dev/null +++ b/ui-ngx/src/assets/linux.svg @@ -0,0 +1 @@ + diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 3374338761..8e254a655c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -880,7 +880,8 @@ "loading": "Loading...", "proceed": "Proceed", "open-details-page": "Open details page", - "not-found": "Not found" + "not-found": "Not found", + "documentation": "Documentation" }, "content-type": { "json": "Json", @@ -1389,16 +1390,10 @@ "device-created-check-connectivity": "Device created. Let's check connectivity!", "loading-check-connectivity-command": "Loading check connectivity commands...", "use-following-instructions": "Use the following instructions for sending telemetry on behalf of the device using shell", - "install-curl": "Install cURL tool.", - "install-mqtt-client": "Install mgtt client tool.", - "install-coap-cli": "Install coap-cli tool.", - "http-command": "HTTP (Linux, macOS or Windows)", - "https-command": "HTTPS (Linux, macOS or Windows)", - "mqtt-command": "MQTT (Linux, macOS)", - "mqtts-command": "MQTT over SSL (Linux, macOS)", + "execute-following-command": "Executive the following command", + "install-curl-windows": "Starting Windows 10 b17063, cURL is available by default", + "install-necessary-client-tools": "Install necessary client tools", "mqtts-x509-command": "Use the following documentation to connect the device via MQTT with authorization X509", - "coap-command": "CoAP (Linux, macOS)", - "coaps-command": "CoAP over DTLS (Linux, macOS)", "coaps-x509-command": "Use the following documentation to connect the device via CoAP over DTLS with authorization X509", "snmp-command": "Use the following documentation to connect the device through the SNMP.", "lwm2m-command": "Use the following documentation to connect the device through the LWM2M." diff --git a/ui-ngx/src/assets/macos.svg b/ui-ngx/src/assets/macos.svg new file mode 100644 index 0000000000..c3bac982fb --- /dev/null +++ b/ui-ngx/src/assets/macos.svg @@ -0,0 +1 @@ + diff --git a/ui-ngx/src/assets/windows.svg b/ui-ngx/src/assets/windows.svg new file mode 100644 index 0000000000..1f168c099e --- /dev/null +++ b/ui-ngx/src/assets/windows.svg @@ -0,0 +1 @@ + diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index a01c157e4c..a0d09d42c4 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -144,6 +144,13 @@ &.space-between { justify-content: space-between; } + &.no-border { + border: none; + border-radius: 0; + } + &.no-padding { + padding: 0; + } .mat-divider-vertical { height: 56px; margin-top: -7px; From 03b49f1ddd57419a68b7cdd7ad86659a65db1dfc Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 21 Jul 2023 18:56:06 +0300 Subject: [PATCH 067/166] Clear code --- .../DeviceConnectivityController.java | 1 - .../server/controller/DeviceController.java | 1 - .../DeviceConnectivityControllerTest.java | 41 ------------------- .../controller/DeviceControllerTest.java | 1 + .../dao/device/DeviceConnectivityService.java | 1 - ...e-check-connectivity-dialog.component.html | 32 +++++++-------- 6 files changed, 17 insertions(+), 60 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java index bf745a2033..abd45e0ca3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceConnectivityController.java @@ -42,7 +42,6 @@ import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.net.URISyntaxException; -import java.util.Map; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION; diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 3eb6202aea..d73915b617 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -134,7 +134,6 @@ public class DeviceController extends BaseController { private final TbDeviceService tbDeviceService; - @ApiOperation(value = "Get Device (getDeviceById)", notes = "Fetch the Device object based on the provided Device Id. " + "If the user has the authority of 'TENANT_ADMIN', the server checks that the device is owned by the same tenant. " + diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index b138778025..9fd8990a40 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -15,84 +15,43 @@ */ package org.thingsboard.server.controller; -import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.mockito.AdditionalAnswers; import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; -import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.EntitySubtype; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.OtaPackageInfo; -import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; -import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceCredentialsId; -import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; -import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest; -import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; import org.thingsboard.server.dao.device.DeviceDao; -import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; -import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; -import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; -import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAP; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.COAPS; import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.DOCKER; diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 9ab5f7fde8..1c952bd549 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -84,6 +84,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; + @ContextConfiguration(classes = {DeviceControllerTest.Config.class}) @DaoSqlTest public class DeviceControllerTest extends AbstractControllerTest { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java index 83f35d5566..51643fa1d4 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityService.java @@ -20,7 +20,6 @@ import org.thingsboard.server.common.data.Device; import java.io.IOException; import java.net.URISyntaxException; -import java.util.Map; public interface DeviceConnectivityService { diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html index a595487521..01d2330aa3 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html @@ -144,8 +144,8 @@
@@ -165,8 +165,8 @@