From c75e9d96e5c1726a22660f051a2bad7c622869f0 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 12 Nov 2025 16:10:32 +0200 Subject: [PATCH] Add API to find calculated fields by filter --- .../controller/CalculatedFieldController.java | 97 ++++++++++++++----- .../server/controller/AbstractWebTest.java | 2 +- .../CalculatedFieldControllerTest.java | 51 +++++++++- .../server/dao/cf/CalculatedFieldService.java | 4 + .../common/data/cf/CalculatedFieldFilter.java | 41 ++++++++ .../common/data/cf/CalculatedFieldInfo.java | 34 +++++++ .../dao/cf/BaseCalculatedFieldService.java | 20 ++++ .../server/dao/cf/CalculatedFieldDao.java | 3 + .../dao/sql/cf/CalculatedFieldRepository.java | 9 ++ .../dao/sql/cf/JpaCalculatedFieldDao.java | 12 +++ 10 files changed, 244 insertions(+), 29 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldInfo.java 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 9a24ab91fc..46abed29c8 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java @@ -44,6 +44,8 @@ import org.thingsboard.script.api.tbel.TbelInvokeService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EventInfo; import org.thingsboard.server.common.data.cf.CalculatedField; +import org.thingsboard.server.common.data.cf.CalculatedFieldFilter; +import org.thingsboard.server.common.data.cf.CalculatedFieldInfo; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration; import org.thingsboard.server.common.data.event.EventType; @@ -59,14 +61,18 @@ import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldTbelScriptEngine; import org.thingsboard.server.service.entitiy.cf.TbCalculatedFieldService; +import org.thingsboard.server.service.security.model.SecurityUser; 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.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.thingsboard.server.controller.ControllerConstants.CF_TEXT_SEARCH_DESCRIPTION; @@ -99,30 +105,30 @@ public class CalculatedFieldController extends BaseController { private static final String TEST_SCRIPT_EXPRESSION = "Execute the Script expression and return the result. The format of request: \n\n" - + MARKDOWN_CODE_BLOCK_START - + "{\n" + - " \"expression\": \"var temp = 0; foreach(element: temperature.values) {temp += element.value;} var avgTemperature = temp / temperature.values.size(); var adjustedTemperature = avgTemperature + 0.1 * humidity.value; return {\\\"adjustedTemperature\\\": adjustedTemperature};\",\n" + - " \"arguments\": {\n" + - " \"temperature\": {\n" + - " \"type\": \"TS_ROLLING\",\n" + - " \"timeWindow\": {\n" + - " \"startTs\": 1739775630002,\n" + - " \"endTs\": 65432211,\n" + - " \"limit\": 5\n" + - " },\n" + - " \"values\": [\n" + - " { \"ts\": 1739775639851, \"value\": 23 },\n" + - " { \"ts\": 1739775664561, \"value\": 43 },\n" + - " { \"ts\": 1739775713079, \"value\": 15 },\n" + - " { \"ts\": 1739775999522, \"value\": 34 },\n" + - " { \"ts\": 1739776228452, \"value\": 22 }\n" + - " ]\n" + - " },\n" + - " \"humidity\": { \"type\": \"SINGLE_VALUE\", \"ts\": 1739776478057, \"value\": 23 }\n" + - " }\n" + - "}" - + MARKDOWN_CODE_BLOCK_END - + "\n\n Expected result JSON contains \"output\" and \"error\"."; + + MARKDOWN_CODE_BLOCK_START + + "{\n" + + " \"expression\": \"var temp = 0; foreach(element: temperature.values) {temp += element.value;} var avgTemperature = temp / temperature.values.size(); var adjustedTemperature = avgTemperature + 0.1 * humidity.value; return {\\\"adjustedTemperature\\\": adjustedTemperature};\",\n" + + " \"arguments\": {\n" + + " \"temperature\": {\n" + + " \"type\": \"TS_ROLLING\",\n" + + " \"timeWindow\": {\n" + + " \"startTs\": 1739775630002,\n" + + " \"endTs\": 65432211,\n" + + " \"limit\": 5\n" + + " },\n" + + " \"values\": [\n" + + " { \"ts\": 1739775639851, \"value\": 23 },\n" + + " { \"ts\": 1739775664561, \"value\": 43 },\n" + + " { \"ts\": 1739775713079, \"value\": 15 },\n" + + " { \"ts\": 1739775999522, \"value\": 34 },\n" + + " { \"ts\": 1739776228452, \"value\": 22 }\n" + + " ]\n" + + " },\n" + + " \"humidity\": { \"type\": \"SINGLE_VALUE\", \"ts\": 1739776478057, \"value\": 23 }\n" + + " }\n" + + "}" + + MARKDOWN_CODE_BLOCK_END + + "\n\n Expected result JSON contains \"output\" and \"error\"."; @ApiOperation(value = "Create Or Update Calculated Field (saveCalculatedField)", notes = "Creates or Updates the Calculated Field. When creating calculated field, platform generates Calculated Field Id as " + UUID_WIKI_LINK + @@ -183,6 +189,46 @@ public class CalculatedFieldController extends BaseController { return checkNotNull(tbCalculatedFieldService.findByTenantIdAndEntityId(getTenantId(), entityId, type, pageLink)); } + @ApiOperation(value = "Get calculated fields (getCalculatedFields)", + notes = "Fetch tenant calculated fields based on the filter.") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @GetMapping(value = "/calculatedFields") + public PageData getCalculatedFields(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @Parameter(description = "Calculated field type filter.") + @RequestParam CalculatedFieldType type, + @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, + @Parameter(description = "Name filter.") + @RequestParam(required = false) String name, + @Parameter(description = CF_TEXT_SEARCH_DESCRIPTION) + @RequestParam(required = false) String textSearch, + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name"})) + @RequestParam(required = false) String sortProperty, + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + SecurityUser user = getCurrentUser(); + Set entityTypes; + if (entityType == null) { + entityTypes = CalculatedField.SUPPORTED_ENTITIES.keySet(); + } else { + entityTypes = EnumSet.of(entityType); + } + + CalculatedFieldFilter filter = CalculatedFieldFilter.builder() + .type(type) + .entityTypes(entityTypes) + .entityIds(entities) + .name(name) + .build(); + return calculatedFieldService.findCalculatedFieldsByTenantIdAndFilter(user.getTenantId(), filter, pageLink); + } + @ApiOperation(value = "Delete Calculated Field (deleteCalculatedField)", notes = "Deletes the calculated field. Referencing non-existing Calculated Field Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @@ -295,7 +341,8 @@ 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 6f1446049f..28c215ae08 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -1370,7 +1370,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { return doPost("/api/calculatedField", calculatedField, CalculatedField.class); } - protected PageData getCalculatedFields(EntityId entityId, CalculatedFieldType type, PageLink pageLink) throws Exception { + protected PageData getEntityCalculatedFields(EntityId entityId, CalculatedFieldType type, PageLink pageLink) throws Exception { return doGetTypedWithPageLink("/api/" + entityId.getEntityType() + "/" + entityId.getId() + "/calculatedFields" + (type != null ? "?type=" + type.name() + "&" : "?"), new TypeReference<>() {}, pageLink); } 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 4ebace6ae7..cf398bb693 100644 --- a/application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java @@ -15,13 +15,17 @@ */ package org.thingsboard.server.controller; +import com.fasterxml.jackson.core.type.TypeReference; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.cf.CalculatedField; +import org.thingsboard.server.common.data.cf.CalculatedFieldInfo; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Argument; import org.thingsboard.server.common.data.cf.configuration.ArgumentType; @@ -36,6 +40,7 @@ import org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoor import org.thingsboard.server.common.data.cf.configuration.geofencing.GeofencingCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.geofencing.ZoneGroupConfiguration; 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.EntitySearchDirection; @@ -45,6 +50,7 @@ import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.List; import java.util.Map; +import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; @@ -192,17 +198,56 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest { } @Test - public void testGetCalculatedFields() throws Exception { + public void testGetEntityCalculatedFields() throws Exception { Device testDevice = createDevice("Test device", "1234567890"); CalculatedField calculatedField = getSimpleCalculatedField(testDevice.getId()); calculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class); - assertThat(getCalculatedFields(testDevice.getId(), null, new PageLink(10)).getData()) + assertThat(getEntityCalculatedFields(testDevice.getId(), null, new PageLink(10)).getData()) .singleElement().isEqualTo(calculatedField); - assertThat(getCalculatedFields(testDevice.getId(), CalculatedFieldType.SIMPLE, new PageLink(10)).getData()) + assertThat(getEntityCalculatedFields(testDevice.getId(), CalculatedFieldType.SIMPLE, new PageLink(10)).getData()) .singleElement().isEqualTo(calculatedField); } + @Test + public void testGetCalculatedFieldsByFilter() throws Exception { + Device device = createDevice("Device A", "1234567890"); + CalculatedFieldInfo deviceCalculatedField = new CalculatedFieldInfo( + doPost("/api/calculatedField", getSimpleCalculatedField(device.getId()), CalculatedField.class), + "Device A" + ); + + DeviceProfile deviceProfile = doPost("/api/deviceProfile", createDeviceProfile("Profile A"), DeviceProfile.class); + CalculatedFieldInfo profileCalculatedField = new CalculatedFieldInfo( + doPost("/api/calculatedField", getSimpleCalculatedField(deviceProfile.getId()), CalculatedField.class), + "Profile A" + ); + + List allCalculatedFields = getCalculatedFields(CalculatedFieldType.SIMPLE, null, null, null); + assertThat(allCalculatedFields).contains(deviceCalculatedField, profileCalculatedField); + + List profileLevelCalculatedFields = getCalculatedFields(CalculatedFieldType.SIMPLE, EntityType.DEVICE_PROFILE, null, null); + assertThat(profileLevelCalculatedFields).containsOnly(profileCalculatedField); + + List specificDeviceCalculatedFields = getCalculatedFields(CalculatedFieldType.SIMPLE, EntityType.DEVICE, List.of(device.getUuidId()), null); + assertThat(specificDeviceCalculatedFields).containsOnly(deviceCalculatedField); + + List byNameCalculatedFields = getCalculatedFields(CalculatedFieldType.SIMPLE, null, null, deviceCalculatedField.getName()); + assertThat(byNameCalculatedFields).containsOnly(deviceCalculatedField); + } + + private List getCalculatedFields(CalculatedFieldType type, + EntityType entityType, + List entities, + String name) throws Exception { + return doGetTypedWithPageLink("/api/calculatedFields?type=" + type + "&" + + (entityType != null ? "entityType=" + entityType + "&" : "") + + (entities != null ? "entities=" + String.join(",", + entities.stream().map(UUID::toString).toList()) + "&" : "") + + (name != null ? "name=" + name + "&" : ""), + new TypeReference>() {}, new PageLink(10)).getData(); + } + @Test public void testDeleteCalculatedField() throws Exception { Device testDevice = createDevice("Test device", "1234567890"); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldService.java index bef4675e03..86bb877964 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldService.java @@ -16,6 +16,8 @@ package org.thingsboard.server.dao.cf; import org.thingsboard.server.common.data.cf.CalculatedField; +import org.thingsboard.server.common.data.cf.CalculatedFieldFilter; +import org.thingsboard.server.common.data.cf.CalculatedFieldInfo; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.common.data.id.EntityId; @@ -44,6 +46,8 @@ public interface CalculatedFieldService extends EntityDaoService { PageData findCalculatedFieldsByTenantId(TenantId tenantId, PageLink pageLink); + PageData findCalculatedFieldsByTenantIdAndFilter(TenantId tenantId, CalculatedFieldFilter filter, PageLink pageLink); + PageData findCalculatedFieldsByEntityId(TenantId tenantId, EntityId entityId, CalculatedFieldType type, PageLink pageLink); void deleteCalculatedField(TenantId tenantId, CalculatedFieldId calculatedFieldId); 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 new file mode 100644 index 0000000000..e99ceeeed9 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldFilter.java @@ -0,0 +1,41 @@ +/** + * 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.data.cf; + +import lombok.Builder; +import lombok.Data; +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; + +@Data +@Builder +public class CalculatedFieldFilter { + + @NonNull + private final CalculatedFieldType type; + @NonNull + private final Set entityTypes; + @Nullable + private final List entityIds; + @Nullable + private final String name; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldInfo.java new file mode 100644 index 0000000000..b72613e86f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldInfo.java @@ -0,0 +1,34 @@ +/** + * 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.data.cf; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +public class CalculatedFieldInfo extends CalculatedField { + + private String entityName; + + public CalculatedFieldInfo(CalculatedField calculatedField, String entityName) { + super(calculatedField); + this.entityName = entityName; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/cf/BaseCalculatedFieldService.java b/dao/src/main/java/org/thingsboard/server/dao/cf/BaseCalculatedFieldService.java index d3ca3ccff3..b184c85cd4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/cf/BaseCalculatedFieldService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/cf/BaseCalculatedFieldService.java @@ -19,8 +19,11 @@ import com.google.common.util.concurrent.FluentFuture; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.cf.CalculatedField; +import org.thingsboard.server.common.data.cf.CalculatedFieldFilter; +import org.thingsboard.server.common.data.cf.CalculatedFieldInfo; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration; import org.thingsboard.server.common.data.id.CalculatedFieldId; @@ -30,6 +33,7 @@ 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.AbstractEntityService; +import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.IncorrectParameterException; @@ -37,8 +41,10 @@ import org.thingsboard.server.dao.service.validator.CalculatedFieldDataValidator import java.util.EnumSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -53,6 +59,7 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements public static final String INCORRECT_CALCULATED_FIELD_ID = "Incorrect calculatedFieldId "; public static final String INCORRECT_ENTITY_ID = "Incorrect entityId "; + private final EntityService entityService; private final CalculatedFieldDao calculatedFieldDao; private final CalculatedFieldDataValidator calculatedFieldDataValidator; @@ -140,6 +147,19 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements return calculatedFieldDao.findAllByTenantId(tenantId, pageLink); } + @Override + public PageData findCalculatedFieldsByTenantIdAndFilter(TenantId tenantId, CalculatedFieldFilter filter, PageLink pageLink) { + PageData calculatedFields = calculatedFieldDao.findByTenantIdAndFilter(tenantId, filter, pageLink); + Set entityIds = calculatedFields.getData().stream() + .map(CalculatedField::getEntityId) + .collect(Collectors.toSet()); + Map entityInfos = entityService.fetchEntityInfos(tenantId, null, entityIds); + return calculatedFields.mapData(calculatedField -> { + EntityInfo entityInfo = entityInfos.get(calculatedField.getEntityId()); + return new CalculatedFieldInfo(calculatedField, entityInfo.getName()); + }); + } + @Override public PageData findCalculatedFieldsByEntityId(TenantId tenantId, EntityId entityId, CalculatedFieldType type, PageLink pageLink) { log.trace("Executing findAllByEntityId, entityId [{}], pageLink [{}]", entityId, pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldDao.java b/dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldDao.java index 9e8ee61d4c..c93e770b8e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldDao.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.cf; import org.thingsboard.server.common.data.cf.CalculatedField; +import org.thingsboard.server.common.data.cf.CalculatedFieldFilter; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.common.data.id.EntityId; @@ -49,4 +50,6 @@ public interface CalculatedFieldDao extends Dao { long countByEntityIdAndTypeNot(TenantId tenantId, EntityId entityId, CalculatedFieldType type); + PageData findByTenantIdAndFilter(TenantId tenantId, CalculatedFieldFilter filter, PageLink pageLink); + } 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 6725387fba..31478666c2 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 @@ -42,6 +42,15 @@ 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.entityType IN :entityTypes " + + "AND (:entityIds IS NULL OR cf.entityId IN :entityIds) " + + "AND (:name IS NULL OR cf.name = :name) " + + "AND (:textSearch IS NULL OR ilike(cf.name, CONCAT('%', :textSearch, '%')) = true)") + Page findByTenantIdAndFilter(UUID tenantId, String type, List entityTypes, + List entityIds, String name, String textSearch, Pageable pageable); + List findAllByTenantId(UUID tenantId); List removeAllByTenantIdAndEntityId(UUID tenantId, UUID entityId); 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 3cd9285d78..a2a95bdf77 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 @@ -15,13 +15,16 @@ */ package org.thingsboard.server.dao.sql.cf; +import com.google.common.base.Strings; import jakarta.transaction.Transactional; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.cf.CalculatedField; +import org.thingsboard.server.common.data.cf.CalculatedFieldFilter; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.common.data.id.EntityId; @@ -102,6 +105,15 @@ public class JpaCalculatedFieldDao extends JpaAbstractDao findByTenantIdAndFilter(TenantId tenantId, CalculatedFieldFilter filter, PageLink pageLink) { + return DaoUtil.toPageData(calculatedFieldRepository.findByTenantIdAndFilter(tenantId.getId(), filter.getType().name(), + filter.getEntityTypes().stream().map(Enum::name).toList(), + CollectionUtils.isNotEmpty(filter.getEntityIds()) ? filter.getEntityIds() : null, + Strings.emptyToNull(filter.getName()), + pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); + } + @Override protected Class getEntityClass() { return CalculatedFieldEntity.class;