From 872fc0803f997e43cd3208bc011839ffe55d6d35 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Tue, 16 Dec 2025 10:54:44 +0200 Subject: [PATCH] Enhance calculated fields filter API --- .../controller/CalculatedFieldController.java | 22 ++++++++++++------- .../server/controller/AbstractWebTest.java | 3 ++- .../CalculatedFieldControllerTest.java | 2 ++ .../common/data/cf/CalculatedFieldFilter.java | 7 +++--- .../dao/sql/cf/CalculatedFieldRepository.java | 9 ++++---- .../dao/sql/cf/JpaCalculatedFieldDao.java | 3 ++- 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java b/application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java index 1c6b23084e..0935f47379 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java @@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.http.HttpStatus; @@ -68,7 +69,7 @@ import org.thingsboard.server.service.security.permission.Operation; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; -import java.util.List; +import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -198,12 +199,12 @@ public class CalculatedFieldController extends BaseController { @RequestParam int pageSize, @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, - @Parameter(description = "Calculated field type filter.") - @RequestParam CalculatedFieldType type, + @Parameter(description = "Calculated field types filter.") + @RequestParam(required = false) Set types, @Parameter(description = "Entity type filter. If not specified, calculated fields for all supported entity types will be returned.") @RequestParam(required = false) EntityType entityType, @Parameter(description = "Entities filter. If not specified, calculated fields for entity type filter will be returned.") - @RequestParam(required = false) List entities, + @RequestParam(required = false) Set entities, @Parameter(description = "Name filter. To specify multiple names, duplicate 'name' parameter for each name, for example '?name=name1&name=name2") @RequestParam(required = false) String name, // for Swagger only, retrieved from MultiValueMap params (due to issues when name contains comma) @Parameter(description = CF_TEXT_SEARCH_DESCRIPTION) @@ -215,6 +216,12 @@ public class CalculatedFieldController extends BaseController { @RequestParam MultiValueMap params) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); SecurityUser user = getCurrentUser(); + + if (CollectionUtils.isEmpty(types)) { + types = EnumSet.allOf(CalculatedFieldType.class); + types.remove(CalculatedFieldType.ALARM); + } + Set entityTypes; if (entityType == null) { entityTypes = CalculatedField.SUPPORTED_ENTITIES.keySet(); @@ -223,10 +230,10 @@ public class CalculatedFieldController extends BaseController { } CalculatedFieldFilter filter = CalculatedFieldFilter.builder() - .type(type) + .types(types) .entityTypes(entityTypes) .entityIds(entities) - .names(params.get("name")) + .names(Optional.ofNullable(params.get("name")).map(HashSet::new).orElse(null)) .build(); return calculatedFieldService.findCalculatedFieldsByTenantIdAndFilter(user.getTenantId(), filter, pageLink); } @@ -361,8 +368,7 @@ public class CalculatedFieldController extends BaseController { return; } case CUSTOMER, ASSET, DEVICE -> checkEntityId(referencedEntityId, Operation.READ); - default -> - throw new IllegalArgumentException("Calculated fields do not support '" + entityType + "' for referenced entities."); + default -> throw new IllegalArgumentException("Calculated fields do not support '" + entityType + "' for referenced entities."); } } } 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 80e79280df..017afe9709 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -1441,7 +1441,8 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { EntityType entityType, List entities, List names) throws Exception { - return doGetTypedWithPageLink("/api/calculatedFields?type=" + type + "&" + + return doGetTypedWithPageLink("/api/calculatedFields?" + + (type != null ? "types=" + type + "&" : "") + (entityType != null ? "entityType=" + entityType + "&" : "") + (entities != null ? "entities=" + String.join(",", entities.stream().map(UUID::toString).toList()) + "&" : "") + diff --git a/application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java index 33f6bfa250..cc9098d492 100644 --- a/application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java @@ -259,6 +259,8 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest { List allCalculatedFields = getCalculatedFields(CalculatedFieldType.SIMPLE, null, null, null); assertThat(allCalculatedFields).contains(deviceCalculatedField, profileCalculatedField); + allCalculatedFields = getCalculatedFields(null, null, null, null); + assertThat(allCalculatedFields).contains(deviceCalculatedField, profileCalculatedField); List profileLevelCalculatedFields = getCalculatedFields(CalculatedFieldType.SIMPLE, EntityType.DEVICE_PROFILE, null, null); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldFilter.java index 40283cbaa3..babe2b58ad 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldFilter.java @@ -21,7 +21,6 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.thingsboard.server.common.data.EntityType; -import java.util.List; import java.util.Set; import java.util.UUID; @@ -30,12 +29,12 @@ import java.util.UUID; public class CalculatedFieldFilter { @NonNull - private final CalculatedFieldType type; + private final Set types; @NonNull private final Set entityTypes; @Nullable - private final List entityIds; + private final Set entityIds; @Nullable - private final List names; + private final Set names; } 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 d7bcbd0568..6f3ca515a3 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 @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.dao.model.sql.CalculatedFieldEntity; import java.util.List; +import java.util.Set; import java.util.UUID; public interface CalculatedFieldRepository extends JpaRepository { @@ -43,18 +44,18 @@ public interface CalculatedFieldRepository extends JpaRepository findByTenantIdAndEntityIdAndTypes(UUID tenantId, UUID entityId, List types, String textSearch, Pageable pageable); @Query("SELECT cf FROM CalculatedFieldEntity cf WHERE cf.tenantId = :tenantId " + - "AND cf.type = :type " + + "AND cf.type IN :types " + "AND cf.entityType IN :entityTypes " + "AND (:entityIds IS NULL OR cf.entityId IN :entityIds) " + "AND (:names IS NULL OR cf.name IN :names) " + "AND (:textSearch IS NULL OR ilike(cf.name, CONCAT('%', :textSearch, '%')) = true)") - Page findByTenantIdAndFilter(UUID tenantId, String type, List entityTypes, - List entityIds, List names, String textSearch, Pageable pageable); + Page findByTenantIdAndFilter(UUID tenantId, List types, List entityTypes, + Set entityIds, Set names, String textSearch, Pageable pageable); @Query("SELECT DISTINCT cf.name FROM CalculatedFieldEntity cf " + "WHERE cf.tenantId = :tenantId AND cf.type = :type AND " + "(:textSearch IS NULL OR ilike(cf.name, CONCAT('%', :textSearch, '%')) = true)") - Page findNamesByTenantIdAndType(UUID tenantId, String type,String textSearch, Pageable pageable); + Page findNamesByTenantIdAndType(UUID tenantId, String type, 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 94aa11a920..5093c51ba7 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 @@ -107,7 +107,8 @@ public class JpaCalculatedFieldDao extends JpaAbstractDao findByTenantIdAndFilter(TenantId tenantId, CalculatedFieldFilter filter, PageLink pageLink) { - return DaoUtil.toPageData(calculatedFieldRepository.findByTenantIdAndFilter(tenantId.getId(), filter.getType().name(), + return DaoUtil.toPageData(calculatedFieldRepository.findByTenantIdAndFilter(tenantId.getId(), + filter.getTypes().stream().map(Enum::name).toList(), filter.getEntityTypes().stream().map(Enum::name).toList(), CollectionUtils.isNotEmpty(filter.getEntityIds()) ? filter.getEntityIds() : null, CollectionUtils.isNotEmpty(filter.getNames()) ? filter.getNames() : null,