From 7a638c2151c6bc4c1cc7e2ea0b1d11766edfd463 Mon Sep 17 00:00:00 2001 From: IrynaMatveieva Date: Fri, 9 Jan 2026 14:33:05 +0200 Subject: [PATCH] updated metric default value to double and added logic to handle relations when direction to --- ...alculatedFieldManagerMessageProcessor.java | 22 +++++------------- ...tractCalculatedFieldProcessingService.java | 16 ++----------- ...titiesAggregationCalculatedFieldState.java | 18 +++++++++++++++ .../RelatedEntitiesArgumentEntry.java | 23 +++++++++---------- .../utils/CalculatedFieldArgumentUtils.java | 4 ++-- .../EntityAggregationCalculatedFieldTest.java | 4 ++-- .../CalculatedFieldControllerTest.java | 2 +- .../configuration/aggregation/AggMetric.java | 2 +- .../dao/relation/BaseRelationService.java | 6 +++-- 9 files changed, 47 insertions(+), 50 deletions(-) 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 f06c0f113a..452435ae9d 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 @@ -595,22 +595,12 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware Predicate matchesCfEntity = relatedEntity -> cfEntityId.equals(relatedEntity) || cfEntityId.equals(getProfileId(tenantId, relatedEntity)); if (byRelationPathQuery != null && !byRelationPathQuery.isEmpty()) { switch (relation.direction()) { - case FROM -> { - if (byRelationPathQuery.size() > 1) { - throw new IllegalStateException("More than one relation found with direction 'TO' " + - "for relation type '" + relation.relationType() + "'. Found: " + byRelationPathQuery.size()); - } - EntityRelation entityRelation = byRelationPathQuery.get(0); // only one supported - EntityId relatedId = entityRelation.getFrom(); - if (matchesCfEntity.test(relatedId)) { - result.add(new CalculatedFieldEntityCtxId(tenantId, cf.getCfId(), relatedId)); - } - } - case TO -> { - byRelationPathQuery.stream() - .filter(entityRelation -> matchesCfEntity.test(entityRelation.getTo())) - .forEach(entityRelation -> result.add(new CalculatedFieldEntityCtxId(tenantId, cf.getCfId(), entityRelation.getTo()))); - } + case FROM -> byRelationPathQuery.stream() + .filter(entityRelation -> matchesCfEntity.test(entityRelation.getFrom())) + .forEach(entityRelation -> result.add(new CalculatedFieldEntityCtxId(tenantId, cf.getCfId(), entityRelation.getFrom()))); + case TO -> byRelationPathQuery.stream() + .filter(entityRelation -> matchesCfEntity.test(entityRelation.getTo())) + .forEach(entityRelation -> result.add(new CalculatedFieldEntityCtxId(tenantId, cf.getCfId(), entityRelation.getTo()))); } } } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java b/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java index 8284e81f70..8cc0043198 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java @@ -247,20 +247,8 @@ public abstract class AbstractCalculatedFieldProcessingService { } return switch (relation.direction()) { - case FROM -> relations.stream() - .map(EntityRelation::getTo) - .toList(); - case TO -> { - if (relations.size() > 1) { - throw new IllegalStateException("More than one relation found with direction 'TO' " + - "for relation type '" + relation.relationType() + "'. Found: " + relations.size()); - } - yield relations.stream() - .map(EntityRelation::getFrom) - .findFirst() - .map(List::of) - .orElseGet(Collections::emptyList); - } + case FROM -> relations.stream().map(EntityRelation::getTo).toList(); + case TO -> relations.stream().map(EntityRelation::getFrom).toList(); }; }, calculatedFieldCallbackExecutor); } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesAggregationCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesAggregationCalculatedFieldState.java index 357e3b66d3..cb64e03d90 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesAggregationCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesAggregationCalculatedFieldState.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.cf.configuration.aggregation.AggKeyInp import org.thingsboard.server.common.data.cf.configuration.aggregation.AggMetric; import org.thingsboard.server.common.data.cf.configuration.aggregation.RelatedEntitiesAggregationCalculatedFieldConfiguration; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.service.cf.CalculatedFieldResult; import org.thingsboard.server.service.cf.TelemetryCalculatedFieldResult; @@ -300,8 +301,25 @@ public class RelatedEntitiesAggregationCalculatedFieldState extends BaseCalculat if (argumentEntry == null || argumentEntry.isEmpty()) { return ReadinessStatus.notReady(MISSING_AGGREGATION_ENTITIES_ERROR); } + if (argumentEntry instanceof RelatedEntitiesArgumentEntry relatedEntitiesArgumentEntry) { + try { + checkConstraintByDirection(relatedEntitiesArgumentEntry); + } catch (Exception e) { + return ReadinessStatus.notReady(e.getMessage()); + } + } } return ReadinessStatus.READY; } + public void checkConstraintByDirection(RelatedEntitiesArgumentEntry relatedEntitiesArgumentEntry) { + if (ctx.getCalculatedField().getConfiguration() instanceof RelatedEntitiesAggregationCalculatedFieldConfiguration config) { + if (EntitySearchDirection.TO == config.getRelation().direction()) { + if (relatedEntitiesArgumentEntry.getEntityInputs().size() > 1) { + throw new IllegalArgumentException("More than one related entity is not supported for relation direction 'TO'. Found: " + relatedEntitiesArgumentEntry.getEntityInputs().size() + "."); + } + } + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesArgumentEntry.java index c1c8d38316..7392ff1e0f 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesArgumentEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesArgumentEntry.java @@ -66,29 +66,28 @@ public class RelatedEntitiesArgumentEntry implements ArgumentEntry, HasLatestTs @Override public boolean updateEntry(ArgumentEntry entry, CalculatedFieldCtx ctx) { if (entry instanceof RelatedEntitiesArgumentEntry relatedEntitiesArgumentEntry) { - checkMaxRelatedEntitiesPerArgument(ctx); + checkRelatedEntitiesNumber(ctx); entityInputs.putAll(relatedEntitiesArgumentEntry.entityInputs); - return true; } else if (entry instanceof SingleValueArgumentEntry singleValueArgumentEntry) { if (entry.isForceResetPrevious()) { - checkMaxRelatedEntitiesPerArgument(ctx); + checkRelatedEntitiesNumber(ctx); entityInputs.put(singleValueArgumentEntry.getEntityId(), singleValueArgumentEntry); - return true; - } - ArgumentEntry argumentEntry = entityInputs.get(singleValueArgumentEntry.getEntityId()); - if (argumentEntry != null) { - argumentEntry.updateEntry(singleValueArgumentEntry, ctx); } else { - checkMaxRelatedEntitiesPerArgument(ctx); - entityInputs.put(singleValueArgumentEntry.getEntityId(), singleValueArgumentEntry); + ArgumentEntry argumentEntry = entityInputs.get(singleValueArgumentEntry.getEntityId()); + if (argumentEntry != null) { + argumentEntry.updateEntry(singleValueArgumentEntry, ctx); + } else { + checkRelatedEntitiesNumber(ctx); + entityInputs.put(singleValueArgumentEntry.getEntityId(), singleValueArgumentEntry); + } } - return true; } else { throw new IllegalArgumentException("Unsupported argument entry type for aggregation argument entry: " + entry.getType()); } + return true; } - private void checkMaxRelatedEntitiesPerArgument(CalculatedFieldCtx ctx) { + private void checkRelatedEntitiesNumber(CalculatedFieldCtx ctx) { if (entityInputs.size() >= ctx.getMaxRelatedEntitiesPerCfArgument()) { throw new IllegalArgumentException( "Exceeded the maximum allowed related entities per argument '" diff --git a/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java b/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java index d23e2900da..fcf8c0593b 100644 --- a/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldArgumentUtils.java @@ -68,9 +68,9 @@ public class CalculatedFieldArgumentUtils { } public static ArgumentEntry createDefaultMetricArgumentEntry(String argKey, AggMetric metric) { - Long defaultValue = metric.getDefaultValue(); + Double defaultValue = metric.getDefaultValue(); if (defaultValue != null) { - return ArgumentEntry.createSingleValueArgument(new DoubleDataEntry(argKey, defaultValue.doubleValue())); + return ArgumentEntry.createSingleValueArgument(new DoubleDataEntry(argKey, defaultValue)); } return new SingleValueArgumentEntry(); } diff --git a/application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java b/application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java index 6c08fc1458..0872e21dae 100644 --- a/application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java +++ b/application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java @@ -254,7 +254,7 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest AggMetric consumption = new AggMetric(); consumption.setFunction(AggFunction.SUM); consumption.setInput(new AggKeyInput("en")); - consumption.setDefaultValue(9999L); + consumption.setDefaultValue(9999.0); aggMetrics.put("consumption", consumption); AggMetric avgEnergyConsumption = new AggMetric(); @@ -319,7 +319,7 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest AggMetric consumption = new AggMetric(); consumption.setFunction(AggFunction.SUM); consumption.setInput(new AggKeyInput("en")); - consumption.setDefaultValue(9999L); + consumption.setDefaultValue(9999.0); aggMetrics.put("consumption", consumption); AggMetric avgTemperature = new AggMetric(); 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 396c381255..d9ec187e39 100644 --- a/application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java @@ -382,7 +382,7 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest { AggMetric metric = new AggMetric(); metric.setInput(new AggKeyInput("en")); - metric.setDefaultValue(9999L); + metric.setDefaultValue(9999.0); config.setMetrics(Map.of("consumption", metric)); config.setWatermark(new Watermark(TimeUnit.DAYS.toSeconds(1))); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/AggMetric.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/AggMetric.java index ea841e24eb..8a322bd9ce 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/AggMetric.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/AggMetric.java @@ -27,6 +27,6 @@ public class AggMetric { private AggFunction function; private String filter; private AggInput input; - private Long defaultValue; + private Double defaultValue; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index b390b732d5..166fba81ff 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -52,11 +52,11 @@ import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.eventsourcing.RelationActionEvent; -import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.sql.JpaExecutorService; import org.thingsboard.server.dao.sql.relation.JpaRelationQueryExecutorService; import org.thingsboard.server.dao.usagerecord.ApiLimitService; +import org.thingsboard.server.exception.DataValidationException; import java.util.ArrayList; import java.util.Collections; @@ -547,7 +547,9 @@ class BaseRelationService implements RelationService { case FROM -> findByFromAndType(tenantId, relationPathQuery.rootEntityId(), relationPathLevel.relationType(), RelationTypeGroup.COMMON); case TO -> findByToAndType(tenantId, relationPathQuery.rootEntityId(), relationPathLevel.relationType(), RelationTypeGroup.COMMON); }; - return relations.size() > limit ? relations.subList(0, limit) : relations; + ArrayList entityRelations = new ArrayList<>(relations); + entityRelations.sort(Comparator.comparing(r -> r.getFrom().getId())); + return entityRelations.size() > limit ? entityRelations.subList(0, limit) : entityRelations; } return relationDao.findByRelationPathQuery(tenantId, relationPathQuery, limit); }