From d962f89c360dc6aad99c6dbd1279f0114531b469 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 21 Aug 2025 17:04:00 +0300 Subject: [PATCH 01/12] fixed flaky test --- .../server/controller/EntityQueryControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java index 5d7f9ec7d6..1f80c1f379 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java @@ -953,7 +953,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest { }); - EntityCountQuery countQuery = new EntityCountQuery(entityTypeFilter); + EntityCountQuery countQuery = new EntityCountQuery(entityTypeFilter, keyFilters); countByQueryAndCheck(countQuery, 97); } From 1a0332e6979dd1d1ed0886357605201a184dd208 Mon Sep 17 00:00:00 2001 From: ArtemDzhereleiko Date: Thu, 21 Aug 2025 18:54:28 +0300 Subject: [PATCH 02/12] UI: Remove schema from repsonse and fixed initial form status --- .../components/rule-node/external/ai-config.component.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.ts index 4989325801..bfba25afa1 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.ts @@ -79,6 +79,9 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent { if (!this.aiConfigForm.get('systemPrompt').value) { delete config.systemPrompt; } + if (this.aiConfigForm.get('responseFormat.type').value !== ResponseFormat.JSON_SCHEMA) { + delete config.responseFormat.schema; + } return deepTrim(config); } @@ -88,10 +91,10 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent { if (this.aiConfigForm.get('responseFormat.type').value !== ResponseFormat.TEXT) { this.aiConfigForm.get('responseFormat.type').patchValue(ResponseFormat.TEXT, {emitEvent: true}); } - this.aiConfigForm.get('responseFormat.type').disable(); + this.aiConfigForm.get('responseFormat.type').disable({emitEvent: false}); } } else { - this.aiConfigForm.get('responseFormat.type').enable(); + this.aiConfigForm.get('responseFormat.type').enable({emitEvent: false}); } } From 3c6d7f22b88e57582cf2cf3f00e974ac65d8ea7a Mon Sep 17 00:00:00 2001 From: devaskim Date: Tue, 26 Aug 2025 22:05:18 +0500 Subject: [PATCH 03/12] Configure default response timeout of rule engine api on server level. --- .../server/controller/RuleEngineController.java | 10 +++++++--- application/src/main/resources/thingsboard.yml | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleEngineController.java b/application/src/main/java/org/thingsboard/server/controller/RuleEngineController.java index befd0baec5..6a66887989 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleEngineController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleEngineController.java @@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.Parameter; import jakarta.annotation.Nullable; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -60,7 +61,10 @@ import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_ @RequestMapping(TbUrlConstants.RULE_ENGINE_URL_PREFIX) @Slf4j public class RuleEngineController extends BaseController { - public static final int DEFAULT_TIMEOUT = 10000; + + @Value("${server.rest.rule_engine.response_timeout:10000}") + public int defaultResponseTimeout; + private static final String MSG_DESCRIPTION_PREFIX = "Creates the Message with type 'REST_API_REQUEST' and payload taken from the request body. "; private static final String MSG_DESCRIPTION = "This method allows you to extend the regular platform API with the power of Rule Engine. You may use default and custom rule nodes to handle the message. " + "The generated message contains two important metadata fields:\n\n" + @@ -85,7 +89,7 @@ public class RuleEngineController extends BaseController { public DeferredResult handleRuleEngineRequest( @Parameter(description = "A JSON value representing the message.", required = true) @RequestBody String requestBody) throws ThingsboardException { - return handleRuleEngineRequest(null, null, null, DEFAULT_TIMEOUT, requestBody); + return handleRuleEngineRequest(null, null, null, defaultResponseTimeout, requestBody); } @ApiOperation(value = "Push entity message to the rule engine (handleRuleEngineRequest)", @@ -104,7 +108,7 @@ public class RuleEngineController extends BaseController { @PathVariable("entityId") String entityIdStr, @Parameter(description = "A JSON value representing the message.", required = true) @RequestBody String requestBody) throws ThingsboardException { - return handleRuleEngineRequest(entityType, entityIdStr, null, DEFAULT_TIMEOUT, requestBody); + return handleRuleEngineRequest(entityType, entityIdStr, null, defaultResponseTimeout, requestBody); } @ApiOperation(value = "Push entity message with timeout to the rule engine (handleRuleEngineRequest)", diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 54c1442ea7..cf29b7c8be 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -100,6 +100,8 @@ server: rate_limits: # Limit that prohibits resetting the password for the user too often. The value of the rate limit. By default, no more than 5 requests per hour reset_password_per_user: "${RESET_PASSWORD_PER_USER_RATE_LIMIT_CONFIGURATION:5:3600}" + rule_engine: + response_timeout: "${DEFAULT_RULE_ENGINE_RESPONSE_TIMEOUT:10000}" # Application info parameters app: From f6920bdbf7151486e13cbfba87c006272b0c838b Mon Sep 17 00:00:00 2001 From: wangxin Date: Fri, 15 Aug 2025 10:01:01 +0800 Subject: [PATCH 04/12] fix: Fixed the issue that textSearch is invalid when querying calculated field --- .../server/dao/sql/cf/CalculatedFieldRepository.java | 6 +++++- .../server/dao/sql/cf/JpaCalculatedFieldDao.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/cf/CalculatedFieldRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/cf/CalculatedFieldRepository.java index 8ccdb88db0..7755ef036b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/cf/CalculatedFieldRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/cf/CalculatedFieldRepository.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.sql.cf; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.dao.model.sql.CalculatedFieldEntity; @@ -36,7 +37,10 @@ public interface CalculatedFieldRepository extends JpaRepository findAllByTenantId(UUID tenantId, Pageable pageable); - Page findAllByTenantIdAndEntityId(UUID tenantId, UUID entityId, Pageable pageable); + @Query("SELECT cf FROM CalculatedFieldEntity cf WHERE cf.tenantId = :tenantId " + + "AND cf.entityId = :entityId " + + "AND (:textSearch IS NULL OR ilike(cf.name, CONCAT('%', :textSearch, '%')) = true)") + Page findAllByTenantIdAndEntityId(UUID tenantId, UUID entityId, String textSearch, Pageable pageable); List findAllByTenantId(UUID tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/cf/JpaCalculatedFieldDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/cf/JpaCalculatedFieldDao.java index 2632b0237b..385839dded 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/cf/JpaCalculatedFieldDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/cf/JpaCalculatedFieldDao.java @@ -85,7 +85,7 @@ public class JpaCalculatedFieldDao extends JpaAbstractDao findAllByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink) { log.debug("Try to find calculated fields by entityId[{}] and pageLink [{}]", entityId, pageLink); - return DaoUtil.toPageData(calculatedFieldRepository.findAllByTenantIdAndEntityId(tenantId.getId(), entityId.getId(), DaoUtil.toPageable(pageLink))); + return DaoUtil.toPageData(calculatedFieldRepository.findAllByTenantIdAndEntityId(tenantId.getId(), entityId.getId(), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); } @Override From 38707da5af56f20024a6862eb87f717763e3dfe8 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 28 Aug 2025 17:09:08 +0200 Subject: [PATCH 05/12] move bitnami to bitnamilegacy - test scope --- .../thingsboard/server/dao/AbstractRedisClusterContainer.java | 2 +- .../java/org/thingsboard/server/dao/AbstractRedisContainer.java | 2 +- .../test/java/org/thingsboard/server/dao/RedisJUnit5Test.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dao/src/test/java/org/thingsboard/server/dao/AbstractRedisClusterContainer.java b/dao/src/test/java/org/thingsboard/server/dao/AbstractRedisClusterContainer.java index a1009cd255..5ffb9f90f7 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/AbstractRedisClusterContainer.java +++ b/dao/src/test/java/org/thingsboard/server/dao/AbstractRedisClusterContainer.java @@ -31,7 +31,7 @@ import java.util.concurrent.TimeUnit; public class AbstractRedisClusterContainer { static final String NODES = "127.0.0.1:6371,127.0.0.1:6372,127.0.0.1:6373,127.0.0.1:6374,127.0.0.1:6375,127.0.0.1:6376"; - static final String IMAGE = "bitnami/valkey-cluster:8.0"; + static final String IMAGE = "bitnamilegacy/valkey-cluster:8.0"; static final Map ENVS = Map.of( "VALKEY_CLUSTER_ANNOUNCE_IP", "127.0.0.1", "VALKEY_CLUSTER_DYNAMIC_IPS", "no", diff --git a/dao/src/test/java/org/thingsboard/server/dao/AbstractRedisContainer.java b/dao/src/test/java/org/thingsboard/server/dao/AbstractRedisContainer.java index 58843a4651..4b5729fcc6 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/AbstractRedisContainer.java +++ b/dao/src/test/java/org/thingsboard/server/dao/AbstractRedisContainer.java @@ -27,7 +27,7 @@ import java.util.List; public class AbstractRedisContainer { @ClassRule(order = 0) - public static GenericContainer redis = new GenericContainer("bitnami/valkey:8.0") + public static GenericContainer redis = new GenericContainer("bitnamilegacy/valkey:8.0") .withEnv("ALLOW_EMPTY_PASSWORD","yes") .withLogConsumer(s -> log.warn(((OutputFrame) s).getUtf8String().trim())) .withExposedPorts(6379); diff --git a/dao/src/test/java/org/thingsboard/server/dao/RedisJUnit5Test.java b/dao/src/test/java/org/thingsboard/server/dao/RedisJUnit5Test.java index 5d43ffb4ae..0def73f9e9 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/RedisJUnit5Test.java +++ b/dao/src/test/java/org/thingsboard/server/dao/RedisJUnit5Test.java @@ -33,7 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class RedisJUnit5Test { @Container - private static final GenericContainer REDIS = new GenericContainer("bitnami/valkey:8.0") + private static final GenericContainer REDIS = new GenericContainer("bitnamilegacy/valkey:8.0") .withEnv("ALLOW_EMPTY_PASSWORD","yes") .withLogConsumer(s -> log.error(((OutputFrame) s).getUtf8String().trim())) .withExposedPorts(6379); From b50a4599ec6236cd37140587892fa6bd3d007bd9 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 28 Aug 2025 17:10:05 +0200 Subject: [PATCH 06/12] move bitnami to bitnamilegacy - docker compose --- docker/docker-compose.kafka.yml | 2 +- docker/docker-compose.valkey-cluster.yml | 12 ++++++------ docker/docker-compose.valkey-sentinel.yml | 6 +++--- docker/docker-compose.valkey.yml | 2 +- .../src/test/resources/docker-compose.valkey-ssl.yml | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docker/docker-compose.kafka.yml b/docker/docker-compose.kafka.yml index 026070cc89..06bafafc61 100644 --- a/docker/docker-compose.kafka.yml +++ b/docker/docker-compose.kafka.yml @@ -17,7 +17,7 @@ services: kafka: restart: always - image: "bitnami/kafka:4.0" + image: "bitnamilegacy/kafka:4.0" ports: - "9092:9092" env_file: diff --git a/docker/docker-compose.valkey-cluster.yml b/docker/docker-compose.valkey-cluster.yml index 242fabf44b..b8ce0f96ec 100644 --- a/docker/docker-compose.valkey-cluster.yml +++ b/docker/docker-compose.valkey-cluster.yml @@ -18,7 +18,7 @@ services: # Valkey cluster # The latest version of Valkey compatible with ThingsBoard is 8.0 valkey-node-0: - image: bitnami/valkey-cluster:8.0 + image: bitnamilegacy/valkey-cluster:8.0 volumes: - ./tb-node/valkey-cluster-data-0:/bitnami/valkey/data environment: @@ -26,7 +26,7 @@ services: - 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5' valkey-node-1: - image: bitnami/valkey-cluster:8.0 + image: bitnamilegacy/valkey-cluster:8.0 volumes: - ./tb-node/valkey-cluster-data-1:/bitnami/valkey/data depends_on: @@ -36,7 +36,7 @@ services: - 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5' valkey-node-2: - image: bitnami/valkey-cluster:8.0 + image: bitnamilegacy/valkey-cluster:8.0 volumes: - ./tb-node/valkey-cluster-data-2:/bitnami/valkey/data depends_on: @@ -46,7 +46,7 @@ services: - 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5' valkey-node-3: - image: bitnami/valkey-cluster:8.0 + image: bitnamilegacy/valkey-cluster:8.0 volumes: - ./tb-node/valkey-cluster-data-3:/bitnami/valkey/data depends_on: @@ -56,7 +56,7 @@ services: - 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5' valkey-node-4: - image: bitnami/valkey-cluster:8.0 + image: bitnamilegacy/valkey-cluster:8.0 volumes: - ./tb-node/valkey-cluster-data-4:/bitnami/valkey/data depends_on: @@ -66,7 +66,7 @@ services: - 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5' valkey-node-5: - image: bitnami/valkey-cluster:8.0 + image: bitnamilegacy/valkey-cluster:8.0 volumes: - ./tb-node/valkey-cluster-data-5:/bitnami/valkey/data depends_on: diff --git a/docker/docker-compose.valkey-sentinel.yml b/docker/docker-compose.valkey-sentinel.yml index 3827e0358b..e528a1bb9a 100644 --- a/docker/docker-compose.valkey-sentinel.yml +++ b/docker/docker-compose.valkey-sentinel.yml @@ -18,7 +18,7 @@ services: # Valkey sentinel # The latest version of Valkey compatible with ThingsBoard is 8.0 valkey-primary: - image: 'bitnami/valkey:8.0' + image: 'bitnamilegacy/valkey:8.0' volumes: - ./tb-node/valkey-sentinel-data-primary:/bitnami/valkey/data environment: @@ -26,7 +26,7 @@ services: - 'VALKEY_PASSWORD=thingsboard' valkey-replica: - image: 'bitnami/valkey:8.0' + image: 'bitnamilegacy/valkey:8.0' volumes: - ./tb-node/valkey-sentinel-data-replica:/bitnami/valkey/data environment: @@ -38,7 +38,7 @@ services: - valkey-primary valkey-sentinel: - image: 'bitnami/valkey-sentinel:8.0' + image: 'bitnamilegacy/valkey-sentinel:8.0' volumes: - ./tb-node/valkey-sentinel-data-sentinel:/bitnami/valkey/data environment: diff --git a/docker/docker-compose.valkey.yml b/docker/docker-compose.valkey.yml index d4a620820d..3eba086a9b 100644 --- a/docker/docker-compose.valkey.yml +++ b/docker/docker-compose.valkey.yml @@ -19,7 +19,7 @@ services: # The latest version of Valkey compatible with ThingsBoard is 8.0 valkey: restart: always - image: bitnami/valkey:8.0 + image: bitnamilegacy/valkey:8.0 environment: # ALLOW_EMPTY_PASSWORD is recommended only for development. ALLOW_EMPTY_PASSWORD: "yes" diff --git a/msa/black-box-tests/src/test/resources/docker-compose.valkey-ssl.yml b/msa/black-box-tests/src/test/resources/docker-compose.valkey-ssl.yml index 8c3029cda1..710dab4690 100644 --- a/msa/black-box-tests/src/test/resources/docker-compose.valkey-ssl.yml +++ b/msa/black-box-tests/src/test/resources/docker-compose.valkey-ssl.yml @@ -19,7 +19,7 @@ services: # The latest version of Valkey compatible with ThingsBoard is 8.0 valkey: restart: always - image: bitnami/valkey:8.0 + image: bitnamilegacy/valkey:8.0 environment: # ALLOW_EMPTY_PASSWORD is recommended only for development. - 'ALLOW_EMPTY_PASSWORD=yes' From 61a39c2b3350ff0dfa59ec6251a3420a9dd45c8e Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Fri, 29 Aug 2025 09:38:44 +0300 Subject: [PATCH 07/12] fixed duplicated cf cache init in cf processor --- .../server/actors/app/AppActor.java | 3 - .../CalculatedFieldManagerActor.java | 12 ---- ...alculatedFieldManagerMessageProcessor.java | 61 +++++++------------ .../server/actors/tenant/TenantActor.java | 3 - .../cf/DefaultCalculatedFieldCache.java | 8 --- .../server/common/msg/MsgType.java | 3 - .../common/msg/cf/CalculatedFieldInitMsg.java | 34 ----------- .../CalculatedFieldInitProfileEntityMsg.java | 36 ----------- .../msg/cf/CalculatedFieldLinkInitMsg.java | 34 ----------- 9 files changed, 22 insertions(+), 172 deletions(-) delete mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitMsg.java delete mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitProfileEntityMsg.java delete mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldLinkInitMsg.java diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index a79a182fa1..27bdec2422 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -114,9 +114,6 @@ public class AppActor extends ContextAwareActor { ctx.broadcastToChildrenByType(msg, EntityType.TENANT); break; case CF_CACHE_INIT_MSG: - case CF_INIT_PROFILE_ENTITY_MSG: - case CF_INIT_MSG: - case CF_LINK_INIT_MSG: case CF_STATE_RESTORE_MSG: //TODO: use priority from the message body. For example, messages about CF lifecycle are important and Device lifecycle are not. // same for the Linked telemetry. diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java index 9f59a80e67..7d2ae0ff44 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java @@ -24,9 +24,6 @@ import org.thingsboard.server.common.msg.TbActorStopReason; import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldCacheInitMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg; -import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg; -import org.thingsboard.server.common.msg.cf.CalculatedFieldInitProfileEntityMsg; -import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg; /** @@ -70,15 +67,6 @@ public class CalculatedFieldManagerActor extends AbstractCalculatedFieldActor { case CF_CACHE_INIT_MSG: processor.onCacheInitMsg((CalculatedFieldCacheInitMsg) msg); break; - case CF_INIT_PROFILE_ENTITY_MSG: - processor.onProfileEntityMsg((CalculatedFieldInitProfileEntityMsg) msg); - break; - case CF_INIT_MSG: - processor.onFieldInitMsg((CalculatedFieldInitMsg) msg); - break; - case CF_LINK_INIT_MSG: - processor.onLinkInitMsg((CalculatedFieldLinkInitMsg) msg); - break; case CF_STATE_RESTORE_MSG: processor.onStateRestoreMsg((CalculatedFieldStateRestoreMsg) msg); break; diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java index ba1ca71bda..c00995d3d4 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java @@ -35,9 +35,6 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.msg.cf.CalculatedFieldCacheInitMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg; -import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg; -import org.thingsboard.server.common.msg.cf.CalculatedFieldInitProfileEntityMsg; -import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -120,37 +117,6 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware msg.getCallback().onSuccess(); } - public void onProfileEntityMsg(CalculatedFieldInitProfileEntityMsg msg) { - log.debug("[{}] Processing profile entity message.", msg.getTenantId().getId()); - entityProfileCache.add(msg.getProfileEntityId(), msg.getEntityId()); - msg.getCallback().onSuccess(); - } - - public void onFieldInitMsg(CalculatedFieldInitMsg msg) throws CalculatedFieldException { - log.debug("[{}] Processing CF init message.", msg.getCf().getId()); - var cf = msg.getCf(); - var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService()); - try { - cfCtx.init(); - } catch (Exception e) { - throw CalculatedFieldException.builder().ctx(cfCtx).eventEntity(cf.getEntityId()).cause(e).errorMessage("Failed to initialize CF context").build(); - } - calculatedFields.put(cf.getId(), cfCtx); - // We use copy on write lists to safely pass the reference to another actor for the iteration. - // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead) - entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cfCtx); - msg.getCallback().onSuccess(); - } - - public void onLinkInitMsg(CalculatedFieldLinkInitMsg msg) { - log.debug("[{}] Processing CF link init message for entity [{}].", msg.getLink().getCalculatedFieldId(), msg.getLink().getEntityId()); - var link = msg.getLink(); - // We use copy on write lists to safely pass the reference to another actor for the iteration. - // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead) - entityIdCalculatedFieldLinks.computeIfAbsent(link.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(link); - msg.getCallback().onSuccess(); - } - public void onStateRestoreMsg(CalculatedFieldStateRestoreMsg msg) { var cfId = msg.getId().cfId(); var calculatedField = calculatedFields.get(cfId); @@ -566,20 +532,37 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware cfs.forEach(cf -> { log.trace("Processing calculated field record: {}", cf); try { - onFieldInitMsg(new CalculatedFieldInitMsg(cf.getTenantId(), cf)); + initCalculatedField(cf); } catch (CalculatedFieldException e) { log.error("Failed to process calculated field record: {}", cf, e); } }); - calculatedFields.values().forEach(cf -> { - entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cf); - }); PageDataIterable cfls = new PageDataIterable<>(pageLink -> cfDaoService.findAllCalculatedFieldLinksByTenantId(tenantId, pageLink), cfSettings.getInitTenantFetchPackSize()); cfls.forEach(link -> { - onLinkInitMsg(new CalculatedFieldLinkInitMsg(link.getTenantId(), link)); + log.trace("Processing calculated field link record: {}", link); + initCalculatedFieldLink(link); }); } + private void initCalculatedField(CalculatedField cf) throws CalculatedFieldException { + var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService()); + try { + cfCtx.init(); + } catch (Exception e) { + throw CalculatedFieldException.builder().ctx(cfCtx).eventEntity(cf.getEntityId()).cause(e).errorMessage("Failed to initialize CF context").build(); + } + calculatedFields.put(cf.getId(), cfCtx); + // We use copy on write lists to safely pass the reference to another actor for the iteration. + // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead) + entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cfCtx); + } + + private void initCalculatedFieldLink(CalculatedFieldLink link) { + // We use copy on write lists to safely pass the reference to another actor for the iteration. + // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead) + entityIdCalculatedFieldLinks.computeIfAbsent(link.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(link); + } + private void initEntityProfileCache() { PageDataIterable deviceIdInfos = new PageDataIterable<>(pageLink -> deviceService.findProfileEntityIdInfosByTenantId(tenantId, pageLink), cfSettings.getInitTenantFetchPackSize()); for (ProfileEntityIdInfo idInfo : deviceIdInfos) { diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java index 84edd064b5..91d25a633b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java @@ -180,9 +180,6 @@ public class TenantActor extends RuleChainManagerActor { onRuleChainMsg((RuleChainAwareMsg) msg); break; case CF_CACHE_INIT_MSG: - case CF_INIT_PROFILE_ENTITY_MSG: - case CF_INIT_MSG: - case CF_LINK_INIT_MSG: case CF_STATE_RESTORE_MSG: case CF_PARTITIONS_CHANGE_MSG: onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, true); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java index 95fa7aebb1..f510857d68 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java @@ -19,11 +19,9 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; 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.springframework.util.ConcurrentReferenceHashMap; import org.thingsboard.script.api.tbel.TbelInvokeService; -import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.cf.CalculatedFieldLink; import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration; @@ -31,8 +29,6 @@ import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageDataIterable; -import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg; -import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg; import org.thingsboard.server.dao.cf.CalculatedFieldService; import org.thingsboard.server.dao.usagerecord.ApiLimitService; import org.thingsboard.server.queue.util.AfterStartUp; @@ -56,8 +52,6 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache { private final CalculatedFieldService calculatedFieldService; private final TbelInvokeService tbelInvokeService; private final ApiLimitService apiLimitService; - @Lazy - private final ActorSystemContext actorSystemContext; private final ConcurrentMap calculatedFields = new ConcurrentHashMap<>(); private final ConcurrentMap> entityIdCalculatedFields = new ConcurrentHashMap<>(); @@ -75,7 +69,6 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache { cfs.forEach(cf -> { if (cf != null) { calculatedFields.putIfAbsent(cf.getId(), cf); - actorSystemContext.tell(new CalculatedFieldInitMsg(cf.getTenantId(), cf)); } }); calculatedFields.values().forEach(cf -> { @@ -84,7 +77,6 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache { PageDataIterable cfls = new PageDataIterable<>(calculatedFieldService::findAllCalculatedFieldLinks, initFetchPackSize); cfls.forEach(link -> { calculatedFieldLinks.computeIfAbsent(link.getCalculatedFieldId(), id -> new CopyOnWriteArrayList<>()).add(link); - actorSystemContext.tell(new CalculatedFieldLinkInitMsg(link.getTenantId(), link)); }); calculatedFieldLinks.values().stream() .flatMap(List::stream) diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java index f1c404ce16..20043582d7 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java @@ -137,9 +137,6 @@ public enum MsgType { CF_CACHE_INIT_MSG, // Sent to init caches for CF actor; - CF_INIT_PROFILE_ENTITY_MSG, // Sent to init profile entities cache; - CF_INIT_MSG, // Sent to init particular calculated field; - CF_LINK_INIT_MSG, // Sent to init particular calculated field; CF_STATE_RESTORE_MSG, // Sent to restore particular calculated field entity state; CF_PARTITIONS_CHANGE_MSG, // Sent when cluster event occures; diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitMsg.java deleted file mode 100644 index e453d2963c..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitMsg.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright © 2016-2025 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.cf; - -import lombok.Data; -import org.thingsboard.server.common.data.cf.CalculatedField; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg; - -@Data -public class CalculatedFieldInitMsg implements ToCalculatedFieldSystemMsg { - - private final TenantId tenantId; - private final CalculatedField cf; - - @Override - public MsgType getMsgType() { - return MsgType.CF_INIT_MSG; - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitProfileEntityMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitProfileEntityMsg.java deleted file mode 100644 index 66cd2ac441..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitProfileEntityMsg.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright © 2016-2025 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.cf; - -import lombok.Data; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg; - -@Data -public class CalculatedFieldInitProfileEntityMsg implements ToCalculatedFieldSystemMsg { - - private final TenantId tenantId; - private final EntityId profileEntityId; - private final EntityId entityId; - - @Override - public MsgType getMsgType() { - return MsgType.CF_INIT_PROFILE_ENTITY_MSG; - } - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldLinkInitMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldLinkInitMsg.java deleted file mode 100644 index d142eb78d8..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldLinkInitMsg.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright © 2016-2025 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.cf; - -import lombok.Data; -import org.thingsboard.server.common.data.cf.CalculatedFieldLink; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg; - -@Data -public class CalculatedFieldLinkInitMsg implements ToCalculatedFieldSystemMsg { - - private final TenantId tenantId; - private final CalculatedFieldLink link; - - @Override - public MsgType getMsgType() { - return MsgType.CF_LINK_INIT_MSG; - } -} From ba440ba7c1f07dd1f7f3f95c194abfe782025691 Mon Sep 17 00:00:00 2001 From: VIacheslavKlimov Date: Tue, 2 Sep 2025 14:28:03 +0300 Subject: [PATCH 08/12] Cleanup upgrade for 4.3.0 --- .../main/data/upgrade/basic/schema_update.sql | 31 ------ .../DefaultDatabaseSchemaSettingsService.java | 2 +- .../update/DefaultDataUpdateService.java | 59 ------------ .../update/DefaultDataUpdateServiceTest.java | 95 ------------------- 4 files changed, 1 insertion(+), 186 deletions(-) delete mode 100644 application/src/test/java/org/thingsboard/server/service/install/update/DefaultDataUpdateServiceTest.java diff --git a/application/src/main/data/upgrade/basic/schema_update.sql b/application/src/main/data/upgrade/basic/schema_update.sql index add832ea6e..d17aba4267 100644 --- a/application/src/main/data/upgrade/basic/schema_update.sql +++ b/application/src/main/data/upgrade/basic/schema_update.sql @@ -13,34 +13,3 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -- - --- UPDATE OTA PACKAGE EXTERNAL ID START - -ALTER TABLE ota_package - ADD COLUMN IF NOT EXISTS external_id uuid; - -DO -$$ - BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'ota_package_external_id_unq_key') THEN - ALTER TABLE ota_package ADD CONSTRAINT ota_package_external_id_unq_key UNIQUE (tenant_id, external_id); - END IF; - END; -$$; - --- UPDATE OTA PACKAGE EXTERNAL ID END - --- DROP INDEXES THAT DUPLICATE UNIQUE CONSTRAINT START - -DROP INDEX IF EXISTS idx_device_external_id; -DROP INDEX IF EXISTS idx_device_profile_external_id; -DROP INDEX IF EXISTS idx_asset_external_id; -DROP INDEX IF EXISTS idx_entity_view_external_id; -DROP INDEX IF EXISTS idx_rule_chain_external_id; -DROP INDEX IF EXISTS idx_dashboard_external_id; -DROP INDEX IF EXISTS idx_customer_external_id; -DROP INDEX IF EXISTS idx_widgets_bundle_external_id; - --- DROP INDEXES THAT DUPLICATE UNIQUE CONSTRAINT END - -ALTER TABLE mobile_app ADD COLUMN IF NOT EXISTS title varchar(255); \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java index e5bd026fb7..f41a530630 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java @@ -32,7 +32,7 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti // This list should include all versions which are compatible for the upgrade. // The compatibility cycle usually breaks when we have some scripts written in Java that may not work after new release. - private static final List SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.1.0"); + private static final List SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.2.0"); private final ProjectInfo projectInfo; private final JdbcTemplate jdbcTemplate; 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 972d5ff36c..7ef691dc6d 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 @@ -15,8 +15,6 @@ */ package org.thingsboard.server.service.install.update; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -24,12 +22,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageDataIterable; -import org.thingsboard.server.common.data.query.DynamicValue; -import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.component.RuleNodeClassInfo; @@ -129,60 +124,6 @@ public class DefaultDataUpdateService implements DataUpdateService { return ruleNodeIds; } - boolean convertDeviceProfileForVersion330(JsonNode profileData) { - boolean isUpdated = false; - if (profileData.has("alarms") && !profileData.get("alarms").isNull()) { - JsonNode alarms = profileData.get("alarms"); - for (JsonNode alarm : alarms) { - if (alarm.has("createRules")) { - JsonNode createRules = alarm.get("createRules"); - for (AlarmSeverity severity : AlarmSeverity.values()) { - if (createRules.has(severity.name())) { - JsonNode spec = createRules.get(severity.name()).get("condition").get("spec"); - if (convertDeviceProfileAlarmRulesForVersion330(spec)) { - isUpdated = true; - } - } - } - } - if (alarm.has("clearRule") && !alarm.get("clearRule").isNull()) { - JsonNode spec = alarm.get("clearRule").get("condition").get("spec"); - if (convertDeviceProfileAlarmRulesForVersion330(spec)) { - isUpdated = true; - } - } - } - } - return isUpdated; - } - - boolean convertDeviceProfileAlarmRulesForVersion330(JsonNode spec) { - if (spec != null) { - if (spec.has("type") && spec.get("type").asText().equals("DURATION")) { - if (spec.has("value")) { - long value = spec.get("value").asLong(); - var predicate = new FilterPredicateValue<>( - value, null, new DynamicValue<>(null, null, false) - ); - ((ObjectNode) spec).remove("value"); - ((ObjectNode) spec).putPOJO("predicate", predicate); - return true; - } - } else if (spec.has("type") && spec.get("type").asText().equals("REPEATING")) { - if (spec.has("count")) { - int count = spec.get("count").asInt(); - var predicate = new FilterPredicateValue<>( - count, null, new DynamicValue<>(null, null, false) - ); - ((ObjectNode) spec).remove("count"); - ((ObjectNode) spec).putPOJO("predicate", predicate); - return true; - } - } - } - return false; - } - public static boolean getEnv(String name, boolean defaultValue) { String env = System.getenv(name); if (env == null) { diff --git a/application/src/test/java/org/thingsboard/server/service/install/update/DefaultDataUpdateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/install/update/DefaultDataUpdateServiceTest.java deleted file mode 100644 index abfab84491..0000000000 --- a/application/src/test/java/org/thingsboard/server/service/install/update/DefaultDataUpdateServiceTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright © 2016-2025 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.install.update; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.context.ActiveProfiles; -import org.thingsboard.common.util.JacksonUtil; - -import java.io.IOException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.willCallRealMethod; - -@ActiveProfiles("install") -@SpringBootTest(classes = DefaultDataUpdateService.class) -class DefaultDataUpdateServiceTest { - - @MockBean - DefaultDataUpdateService service; - - @BeforeEach - void setUp() { - willCallRealMethod().given(service).convertDeviceProfileAlarmRulesForVersion330(any()); - willCallRealMethod().given(service).convertDeviceProfileForVersion330(any()); - } - - JsonNode readFromResource(String resourceName) throws IOException { - return JacksonUtil.OBJECT_MAPPER.readTree(this.getClass().getClassLoader().getResourceAsStream(resourceName)); - } - - @Test - void convertDeviceProfileAlarmRulesForVersion330FirstRun() throws IOException { - JsonNode spec = readFromResource("update/330/device_profile_001_in.json"); - JsonNode expected = readFromResource("update/330/device_profile_001_out.json"); - - assertThat(service.convertDeviceProfileForVersion330(spec.get("profileData"))).isTrue(); - assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); // use IDE feature - } - - @Test - void convertDeviceProfileAlarmRulesForVersion330SecondRun() throws IOException { - JsonNode spec = readFromResource("update/330/device_profile_001_out.json"); - JsonNode expected = readFromResource("update/330/device_profile_001_out.json"); - - assertThat(service.convertDeviceProfileForVersion330(spec.get("profileData"))).isFalse(); - assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); // use IDE feature - } - - @Test - void convertDeviceProfileAlarmRulesForVersion330EmptyJson() throws JsonProcessingException { - JsonNode spec = JacksonUtil.toJsonNode("{ }"); - JsonNode expected = JacksonUtil.toJsonNode("{ }"); - - assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse(); - assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); - } - - @Test - void convertDeviceProfileAlarmRulesForVersion330AlarmNodeNull() throws JsonProcessingException { - JsonNode spec = JacksonUtil.toJsonNode("{ \"alarms\" : null }"); - JsonNode expected = JacksonUtil.toJsonNode("{ \"alarms\" : null }"); - - assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse(); - assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); - } - - @Test - void convertDeviceProfileAlarmRulesForVersion330NoAlarmNode() throws JsonProcessingException { - JsonNode spec = JacksonUtil.toJsonNode("{ \"configuration\": { \"type\": \"DEFAULT\" } }"); - JsonNode expected = JacksonUtil.toJsonNode("{ \"configuration\": { \"type\": \"DEFAULT\" } }"); - - assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse(); - assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); - } - -} From a7f687443fe3a5760e32d86585e4cc7bc77fc3fb Mon Sep 17 00:00:00 2001 From: VIacheslavKlimov Date: Tue, 2 Sep 2025 14:36:05 +0300 Subject: [PATCH 09/12] Fix license header --- application/src/main/data/upgrade/basic/schema_update.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/application/src/main/data/upgrade/basic/schema_update.sql b/application/src/main/data/upgrade/basic/schema_update.sql index d17aba4267..016e786776 100644 --- a/application/src/main/data/upgrade/basic/schema_update.sql +++ b/application/src/main/data/upgrade/basic/schema_update.sql @@ -13,3 +13,4 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -- + From 94c7c5b651a6b77cdc7270bda59722284bcdff9b Mon Sep 17 00:00:00 2001 From: devaskim Date: Thu, 4 Sep 2025 13:15:10 +0500 Subject: [PATCH 10/12] Added description to server.rest.rule_engine configuration property. --- application/src/main/resources/thingsboard.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index cf29b7c8be..e34f6b52fe 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -101,6 +101,7 @@ server: # Limit that prohibits resetting the password for the user too often. The value of the rate limit. By default, no more than 5 requests per hour reset_password_per_user: "${RESET_PASSWORD_PER_USER_RATE_LIMIT_CONFIGURATION:5:3600}" rule_engine: + # Deafult timeout for waiting response of REST API request to Rule Engine in milliseconds response_timeout: "${DEFAULT_RULE_ENGINE_RESPONSE_TIMEOUT:10000}" # Application info parameters From 468fc68aa7a3c7f91a0b5da0361d94039ba94715 Mon Sep 17 00:00:00 2001 From: devaskim Date: Thu, 4 Sep 2025 13:17:43 +0500 Subject: [PATCH 11/12] Fixed mistype in description of server.rest.rule_engine.response_timeout configuration property. --- application/src/main/resources/thingsboard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index e34f6b52fe..89be36f470 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -101,7 +101,7 @@ server: # Limit that prohibits resetting the password for the user too often. The value of the rate limit. By default, no more than 5 requests per hour reset_password_per_user: "${RESET_PASSWORD_PER_USER_RATE_LIMIT_CONFIGURATION:5:3600}" rule_engine: - # Deafult timeout for waiting response of REST API request to Rule Engine in milliseconds + # Default timeout for waiting response of REST API request to Rule Engine in milliseconds response_timeout: "${DEFAULT_RULE_ENGINE_RESPONSE_TIMEOUT:10000}" # Application info parameters From 88170fb83b83987a81a802c9f0b539a837d2b621 Mon Sep 17 00:00:00 2001 From: Ruslan Vasylkiv Date: Thu, 4 Sep 2025 18:41:21 +0300 Subject: [PATCH 12/12] Update README.md --- README.md | 171 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 137 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index bffd87ffa5..27992d57bd 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,146 @@ -# ThingsBoard -[![ThingsBoard Builds Server Status](https://img.shields.io/teamcity/build/e/ThingsBoard_Build?label=TB%20builds%20server&server=https%3A%2F%2Fbuilds.thingsboard.io&logo=data:image/png;base64,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&labelColor=305680)](https://builds.thingsboard.io/viewType.html?buildTypeId=ThingsBoard_Build&guest=1) +![banner](https://github.com/user-attachments/assets/3584b592-33dd-4fb4-91d4-47b62b34806c) + +
+ +# Open-source IoT platform for data collection, processing, visualization, and device management. + +
+
+
+ +💡 [Get started](https://thingsboard.io/docs/getting-started-guides/helloworld/) • 🌐 [Website](https://thingsboard.io/) • 📚 [Documentation](https://thingsboard.io/docs/) • 📔 [Blog](https://thingsboard.io/blog/) • ▶️ [Live demo](https://demo.thingsboard.io/signup) • 🔗 [LinkedIn](https://www.linkedin.com/company/thingsboard/posts/?feedView=all) + +
+ +## 🚀 Installation options + +* Install ThingsBoard [On-premise](https://thingsboard.io/docs/user-guide/install/installation-options/?ceInstallType=onPremise) +* Try [ThingsBoard Cloud](https://thingsboard.io/installations/) +* or [Use our Live demo](https://demo.thingsboard.io/signup) + +## 💡 Getting started with ThingsBoard + +Check out our [Getting Started guide](https://thingsboard.io/docs/getting-started-guides/helloworld/) or [watch the video](https://www.youtube.com/watch?v=80L0ubQLXsc) to learn the basics of ThingsBoard and create your first dashboard! You will learn to: + +* Connect devices to ThingsBoard +* Push data from devices to ThingsBoard +* Build real-time dashboards +* Create a Customer and assign the dashboard with them. +* Define thresholds and trigger alarms +* Set up notifications via email, SMS, mobile apps, or integrate with third-party services. + +## ✨ Features + + + + + + + + + + +
+
+
+ Provision and manage devices and assets +

Provision and manage
devices and assets

+
+
+

Provision, monitor and control your IoT entities in secure way using rich server-side APIs. Define relations between your devices, assets, customers or any other entities.

+
+ +
+
+
+
+ Collect and visualize your data +

Collect and visualize
your data

+
+
+

Collect and store telemetry data in scalable and fault-tolerant way. Visualize your data with built-in or custom widgets and flexible dashboards. Share dashboards with your customers.

+
+ +
+
+
+
+ SCADA Dashboards +

SCADA Dashboards

+
+
+

Monitor and control your industrial processes in real time with SCADA. Use SCADA symbols on dashboards to create and manage any workflow, offering full flexibility to design and oversee operations according to your requirements.

+
+ +
+
+
+
+ Process and React +

Process and React

+
+
+

Define data processing rule chains. Transform and normalize your device data. Raise alarms on incoming telemetry events, attribute updates, device inactivity and user actions.

+
+
+ +
+
+ +## ⚙️ Powerful IoT Rule Engine + +ThingsBoard allows you to create complex [Rule Chains](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/) to process data from your devices and match your application specific use cases. + +[![IoT Rule Engine](https://github.com/user-attachments/assets/ccc048a8-5aa3-44dc-abd4-c20d1d833102 "IoT Rule Engine")](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/) + +
+ +[**Read more about Rule Engine 🡪**](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/) + +
+ +## 📦 Real-Time IoT Dashboards + +ThingsBoard is a scalable, user-friendly, and device-agnostic IoT platform that speeds up time-to-market with powerful built-in solution templates. It enables data collection and analysis from any devices, saving resources on routine tasks and letting you focus on your solution’s unique aspects. See more our Use Cases [here](https://thingsboard.io/iot-use-cases/). + +[**Smart energy**](https://thingsboard.io/use-cases/smart-energy/) + +[![Smart energy](https://github.com/user-attachments/assets/7952d0f1-2ba4-4989-bfc9-75b40de6ea3f "Smart energy")](https://thingsboard.io/use-cases/smart-energy/) + +[**SCADA swimming pool**](https://thingsboard.io/use-cases/scada/) + +[![SCADA Swimming pool](https://github.com/user-attachments/assets/b357c129-ea72-4b64-9dfe-ac25011603b6 "SCADA Swimming pool")](https://thingsboard.io/use-cases/scada/) + +[**Fleet tracking**](https://thingsboard.io/use-cases/fleet-tracking/) + +[![Fleet tracking](https://github.com/user-attachments/assets/80b63841-40c9-4db9-bec2-6a400dc6e58d "Fleet tracking")](https://thingsboard.io/use-cases/fleet-tracking/) + +[**Smart farming**](https://thingsboard.io/use-cases/smart-farming/) + +[![Smart farming](https://github.com/user-attachments/assets/8fe84ad6-6ea4-4cb1-bc31-6cd5c20c357b "Smart farming")](https://thingsboard.io/use-cases/smart-farming/) -ThingsBoard is an open-source IoT platform for data collection, processing, visualization, and device management. - - - - -## Documentation - -ThingsBoard documentation is hosted on [thingsboard.io](https://thingsboard.io/docs). - -## IoT use cases - -[**Smart energy**](https://thingsboard.io/smart-energy/) -[![Smart energy](https://user-images.githubusercontent.com/8308069/152984256-eb48564a-645c-468d-912b-f554b63104a5.gif "Smart energy")](https://thingsboard.io/smart-energy/) - -[**SCADA Swimming pool**](https://thingsboard.io/use-cases/scada/) -[![SCADA Swimming pool](https://github.com/user-attachments/assets/0878a2f5-d358-47c5-b295-03b4533685cf "SCADA Swimming pool")](https://thingsboard.io/use-cases/scada/) - -[**Fleet tracking**](https://thingsboard.io/fleet-tracking/) -[![Fleet tracking](https://user-images.githubusercontent.com/8308069/152984528-0054ed55-8b8b-4cda-ba45-02fe95a81222.gif "Fleet tracking")](https://thingsboard.io/fleet-tracking/) - -[**Smart farming**](https://thingsboard.io/smart-farming/) -[![Smart farming](https://user-images.githubusercontent.com/8308069/152984443-a98b7d3d-ff7a-4037-9011-e71e1e6f755f.gif "Smart farming")](https://thingsboard.io/smart-farming/) +[**Smart metering**](https://thingsboard.io/smart-metering/) -[**IoT Rule Engine**](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/) -[![IoT Rule Engine](https://img.thingsboard.io/demo/send-email-rule-chain.gif "IoT Rule Engine")](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/) +[![Smart metering](https://github.com/user-attachments/assets/564e5ed0-afad-452c-a16c-6270b468ebdc "Smart metering")](https://thingsboard.io/smart-metering/) -[**Smart metering**](https://thingsboard.io/smart-metering/) -[![Smart metering](https://user-images.githubusercontent.com/8308069/31455788-6888a948-aec1-11e7-9819-410e0ba785e0.gif "Smart metering")](https://thingsboard.io/smart-metering/) +
-## Getting Started +[**Check more of our use cases 🡪**](https://thingsboard.io/iot-use-cases/) -Collect and Visualize your IoT data in minutes by following this [guide](https://thingsboard.io/docs/getting-started-guides/helloworld/). +
-## Support +## 🫶 Support - - [Stackoverflow](http://stackoverflow.com/questions/tagged/thingsboard) +To get support, please visit our [GitHub issues page](https://github.com/thingsboard/thingsboard/issues) -## Licenses +## 📄 Licenses -This project is released under [Apache 2.0 License](./LICENSE). +This project is released under [Apache 2.0 License](./LICENSE)