diff --git a/application/src/main/data/upgrade/basic/schema_update.sql b/application/src/main/data/upgrade/basic/schema_update.sql index d01fa37312..8252a15282 100644 --- a/application/src/main/data/upgrade/basic/schema_update.sql +++ b/application/src/main/data/upgrade/basic/schema_update.sql @@ -47,9 +47,9 @@ SET profile_data = jsonb_set( THEN NULL ELSE to_jsonb(60) END, - 'minAggregationIntervalInSecForCF', + 'minAllowedAggregationIntervalInSecForCF', CASE - WHEN (profile_data -> 'configuration') ? 'minAggregationIntervalInSecForCF' + WHEN (profile_data -> 'configuration') ? 'minAllowedAggregationIntervalInSecForCF' THEN NULL ELSE to_jsonb(60) END @@ -66,7 +66,7 @@ WHERE NOT ( AND (profile_data -> 'configuration') ? 'minAllowedDeduplicationIntervalInSecForCF' AND - (profile_data -> 'configuration') ? 'minAggregationIntervalInSecForCF' + (profile_data -> 'configuration') ? 'minAllowedAggregationIntervalInSecForCF' ); -- UPDATE TENANT PROFILE CONFIGURATION END diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index c8b4c37ead..16081a6d31 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -97,8 +97,8 @@ import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueStatsService; import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.dao.resource.TbResourceDataCache; import org.thingsboard.server.dao.resource.ResourceService; +import org.thingsboard.server.dao.resource.TbResourceDataCache; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleNodeStateService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; @@ -664,10 +664,14 @@ public class ActorSystemContext { @Getter private long cfCalculationResultTimeout; - @Value("${actors.calculated_fields.check_interval:120}") + @Value("${actors.calculated_fields.check_interval:60}") @Getter private long cfCheckInterval; + @Value("${actors.alarms.reevaluation_interval:120}") + @Getter + private long alarmRulesReevaluationInterval; + @Autowired @Getter private MqttClientSettings mqttClientSettings; @@ -851,7 +855,7 @@ public class ActorSystemContext { if (arguments != null) { eventBuilder.arguments(JacksonUtil.toString( arguments.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toTbelCfArg())) + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().jsonValue())) )); } if (result != null) { diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java index 89f5578760..5d9aeb17ff 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java @@ -53,7 +53,6 @@ import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx; import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.SingleValueArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.aggregation.RelatedEntitiesAggregationCalculatedFieldState; -import org.thingsboard.server.service.cf.ctx.state.aggregation.single.EntityAggregationCalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.alarm.AlarmCalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.geofencing.GeofencingArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.geofencing.GeofencingCalculatedFieldState; @@ -124,12 +123,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM if (state != null) { state.setCtx(msg.getCtx(), actorCtx); state.setPartition(msg.getPartition()); - if (state instanceof RelatedEntitiesAggregationCalculatedFieldState relatedEntitiesAggState) { - relatedEntitiesAggState.scheduleReevaluation(); - } - if (state instanceof EntityAggregationCalculatedFieldState entityAggState) { - entityAggState.fillMissingIntervals(); - } + state.init(true); states.put(cfId, state); } else { removeState(cfId); @@ -140,7 +134,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM log.debug("Processing CF state partition restore msg: {}", msg); for (CalculatedFieldState state : states.values()) { if (msg.getPartition().equals(state.getPartition())) { - state.init(); + state.init(false); } } } @@ -455,7 +449,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM private void initState(CalculatedFieldState state, CalculatedFieldCtx ctx) { state.setCtx(ctx, actorCtx); - state.init(); + state.init(false); if (ctx.getCfType() == CalculatedFieldType.GEOFENCING && ctx.isRelationQueryDynamicArguments()) { GeofencingCalculatedFieldState geofencingState = (GeofencingCalculatedFieldState) state; @@ -504,7 +498,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM } else { if (DebugModeUtil.isDebugFailuresAvailable(ctx.getCalculatedField())) { String errorMsg = ctx.isInitialized() ? state.getReadinessStatus().errorMsg() : "Calculated field state is not initialized!"; - systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId, state.getArguments(), tbMsgId, tbMsgType, null, errorMsg); + systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId, state.getArguments(), tbMsgId, tbMsgType, null, errorMsg); } callback.onSuccess(); } diff --git a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java index 7b6d7b5768..9c04cb92bd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java +++ b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java @@ -165,7 +165,7 @@ public class SystemInfoController extends BaseController { systemParams.setMinAllowedScheduledUpdateIntervalInSecForCF(tenantProfileConfiguration.getMinAllowedScheduledUpdateIntervalInSecForCF()); systemParams.setMaxRelationLevelPerCfArgument(tenantProfileConfiguration.getMaxRelationLevelPerCfArgument()); systemParams.setMinAllowedDeduplicationIntervalInSecForCF(tenantProfileConfiguration.getMinAllowedDeduplicationIntervalInSecForCF()); - systemParams.setMinAggregationIntervalInSecForCF(tenantProfileConfiguration.getMinAggregationIntervalInSecForCF()); + systemParams.setMinAllowedAggregationIntervalInSecForCF(tenantProfileConfiguration.getMinAllowedAggregationIntervalInSecForCF()); systemParams.setTrendzSettings(trendzSettingsService.findTrendzSettings(currentUser.getTenantId())); } systemParams.setMobileQrEnabled(Optional.ofNullable(qrCodeSettingService.findQrCodeSettings(TenantId.SYS_TENANT_ID)) diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java index 1a3bdce1f6..6c0f70e17c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java @@ -164,9 +164,14 @@ public class TenantProfileController extends BaseController { " \"warnThreshold\": 0,\n" + " \"maxCalculatedFieldsPerEntity\": 5,\n" + " \"maxArgumentsPerCF\": 10,\n" + + " \"minAllowedScheduledUpdateIntervalInSecForCF\": 60,\n" + + " \"maxRelationLevelPerCfArgument\": 10,\n" + + " \"maxRelatedEntitiesToReturnPerCfArgument\": 100,\n" + " \"maxDataPointsPerRollingArg\": 1000,\n" + " \"maxStateSizeInKBytes\": 32,\n" + " \"maxSingleValueArgumentSizeInKBytes\": 2" + + " \"minAllowedDeduplicationIntervalInSecForCF\": 60" + + " \"minAllowedAggregationIntervalInSecForCF\": 60" + " }\n" + " },\n" + " \"default\": false\n" + 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 6914f6d10a..c22623e109 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 @@ -199,12 +199,13 @@ public abstract class AbstractCalculatedFieldProcessingService { } protected Map> fetchEntityAggArguments(CalculatedFieldCtx ctx, EntityId entityId, long ts) { - EntityAggregationCalculatedFieldConfiguration aggConfig = (EntityAggregationCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration(); - - return aggConfig.getArguments().entrySet().stream() + if (!(ctx.getCalculatedField().getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration config)) { + return Collections.emptyMap(); + } + return config.getArguments().entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, - entry -> fetchTimeSeries(ctx.getTenantId(), entityId, entry.getValue(), aggConfig.getInterval(), ts) + entry -> fetchTimeSeries(ctx.getTenantId(), entityId, entry.getValue(), config.getInterval(), ts) )); } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldCache.java b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldCache.java index 5842363229..d50a125451 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldCache.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldCache.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.cf; import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.cf.CalculatedFieldLink; +import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -25,6 +26,7 @@ import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx; import java.util.List; import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Stream; public interface CalculatedFieldCache { @@ -38,9 +40,7 @@ public interface CalculatedFieldCache { List getCalculatedFieldCtxsByEntityId(EntityId entityId); - List getRelatedEntitiesAggCalculatedFieldCtxsByFilter(Predicate relatedEntityFilter); - - List getEntityAggCalculatedFieldCtxsByFilter(Predicate entityAggCfFilter); + Stream getCalculatedFieldCtxsByType(CalculatedFieldType cfType); boolean hasCalculatedFields(TenantId tenantId, EntityId entityId, Predicate filter); 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 4f2b7a459c..9e51997a20 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 @@ -49,6 +49,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Predicate; +import java.util.stream.Stream; @Service @Slf4j @@ -148,21 +149,10 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache { } @Override - public List getRelatedEntitiesAggCalculatedFieldCtxsByFilter(Predicate relatedEntityFilter) { + public Stream getCalculatedFieldCtxsByType(CalculatedFieldType cfType) { return calculatedFields.values().stream() - .filter(cf -> CalculatedFieldType.RELATED_ENTITIES_AGGREGATION.equals(cf.getType())) - .map(cf -> getCalculatedFieldCtx(cf.getId())) - .filter(relatedEntityFilter) - .toList(); - } - - @Override - public List getEntityAggCalculatedFieldCtxsByFilter(Predicate entityAggCfFilter) { - return calculatedFields.values().stream() - .filter(cf -> CalculatedFieldType.ENTITY_AGGREGATION.equals(cf.getType())) - .map(cf -> getCalculatedFieldCtx(cf.getId())) - .filter(entityAggCfFilter) - .toList(); + .filter(cf -> cfType.equals(cf.getType())) + .map(cf -> getCalculatedFieldCtx(cf.getId())); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldQueueService.java b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldQueueService.java index 1bc1222d6b..36a44b0d74 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldQueueService.java @@ -27,6 +27,7 @@ import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.cf.CalculatedFieldLink; +import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.aggregation.RelatedEntitiesAggregationCalculatedFieldConfiguration; import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.common.data.id.EntityId; @@ -188,13 +189,15 @@ public class DefaultCalculatedFieldQueueService implements CalculatedFieldQueueS } } - List entityAggCfCtxs = calculatedFieldCache.getEntityAggCalculatedFieldCtxsByFilter(filter); - if (!entityAggCfCtxs.isEmpty()) { + boolean hasMatchesEntityAggCfs = calculatedFieldCache.getCalculatedFieldCtxsByType(CalculatedFieldType.ENTITY_AGGREGATION).anyMatch(filter); + if (hasMatchesEntityAggCfs) { return true; } - List relatedEntityAggCfCtxs = calculatedFieldCache.getRelatedEntitiesAggCalculatedFieldCtxsByFilter(relatedEntityFilter); - for (CalculatedFieldCtx cfCtx : relatedEntityAggCfCtxs) { + List relatedEntitiesAggregationCfs = calculatedFieldCache.getCalculatedFieldCtxsByType(CalculatedFieldType.RELATED_ENTITIES_AGGREGATION) + .filter(relatedEntityFilter) + .toList(); + for (CalculatedFieldCtx cfCtx : relatedEntitiesAggregationCfs) { if (cfCtx.getCalculatedField().getConfiguration() instanceof RelatedEntitiesAggregationCalculatedFieldConfiguration aggConfig) { RelationPathLevel relation = aggConfig.getRelation(); EntitySearchDirection inverseDirection = switch (relation.direction()) { diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntry.java index 55a61d1918..dc23ffa979 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ArgumentEntry.java @@ -18,6 +18,8 @@ package org.thingsboard.server.service.cf.ctx.state; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.script.api.tbel.TbelCfArg; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.KvEntry; @@ -54,6 +56,10 @@ public interface ArgumentEntry { boolean isEmpty(); + default JsonNode jsonValue() { + return JacksonUtil.valueToTree(toTbelCfArg()); + } + TbelCfArg toTbelCfArg(); boolean isForceResetPrevious(); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java index 20f944e433..e438274ab6 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java @@ -62,7 +62,7 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState, } @Override - public void init() { + public void init(boolean restored) { } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java index 5c50f7e95e..518b65dc5f 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java @@ -96,9 +96,11 @@ public class CalculatedFieldCtx implements Closeable { private Output output; private String expression; private boolean useLatestTs; - private boolean requiresScheduledReevaluation; - private long aggCheckInterval; + private long cfCheckInterval; + private long alarmReevaluationInterval; + + private long lastReevaluationTs; private ActorSystemContext systemContext; private TbelInvokeService tbelInvokeService; @@ -200,7 +202,8 @@ public class CalculatedFieldCtx implements Closeable { if (calculatedField.getConfiguration() instanceof RelatedEntitiesAggregationCalculatedFieldConfiguration aggConfig) { this.useLatestTs = aggConfig.isUseLatestTs(); } - this.aggCheckInterval = systemContext.getCfCheckInterval(); + this.cfCheckInterval = systemContext.getCfCheckInterval(); + this.alarmReevaluationInterval = systemContext.getAlarmRulesReevaluationInterval(); this.systemContext = systemContext; this.tbelInvokeService = systemContext.getTbelInvokeService(); this.relationService = systemContext.getRelationService(); @@ -213,19 +216,28 @@ public class CalculatedFieldCtx implements Closeable { } public boolean isRequiresScheduledReevaluation() { + long now = System.currentTimeMillis(); + long cfCheckIntervalMillis = TimeUnit.SECONDS.toMillis(systemContext.getCfCheckInterval()); if (calculatedField.getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration entityAggregationConfig) { - long now = System.currentTimeMillis(); Watermark watermark = entityAggregationConfig.getWatermark(); if (watermark != null && watermark.getDuration() > 0) { return true; } - long cfCheckIntervalMillis = TimeUnit.SECONDS.toMillis(systemContext.getCfCheckInterval()); long intervalEndTs = entityAggregationConfig.getInterval().getCurrentIntervalEndTs(); if (now + cfCheckIntervalMillis >= intervalEndTs) { return true; } } - return calculatedField.getConfiguration().requiresScheduledReevaluation(); + long reevaluationIntervalMillis = TimeUnit.SECONDS.toMillis(systemContext.getAlarmRulesReevaluationInterval()); + boolean requiresScheduledReevaluation = calculatedField.getConfiguration().requiresScheduledReevaluation(); + if (requiresScheduledReevaluation) { + if (now + cfCheckIntervalMillis >= lastReevaluationTs + reevaluationIntervalMillis) { + lastReevaluationTs = now; + return true; + } + return false; + } + return false; } public void init() { diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java index 64b67d7ab5..e3914cc125 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java @@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.util.CollectionsUtil; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.service.cf.CalculatedFieldResult; import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId; -import org.thingsboard.server.service.cf.ctx.state.aggregation.single.EntityAggregationCalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.aggregation.RelatedEntitiesAggregationCalculatedFieldState; +import org.thingsboard.server.service.cf.ctx.state.aggregation.single.EntityAggregationCalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.alarm.AlarmCalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.geofencing.GeofencingArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.geofencing.GeofencingCalculatedFieldState; @@ -63,7 +63,7 @@ public interface CalculatedFieldState extends Closeable { void setCtx(CalculatedFieldCtx ctx, TbActorRef actorCtx); - void init(); + void init(boolean restored); Map update(Map arguments, CalculatedFieldCtx ctx); 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 ff50cd99b6..8159b1db67 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 @@ -74,6 +74,14 @@ public class RelatedEntitiesAggregationCalculatedFieldState extends BaseCalculat deduplicationIntervalMs = SECONDS.toMillis(configuration.getDeduplicationIntervalInSec()); } + @Override + public void init(boolean restored) { + super.init(restored); + if (restored) { + scheduleReevaluation(); + } + } + @Override public void close() { super.close(); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/AggIntervalEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/AggIntervalEntry.java index fac4e403a8..acbaffd5d9 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/AggIntervalEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/AggIntervalEntry.java @@ -26,7 +26,7 @@ public class AggIntervalEntry { private Long endTs; public boolean belongsToInterval(long ts) { - return ts >= startTs && ts <= endTs; + return ts >= startTs && ts < endTs; } } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationArgumentEntry.java index 9ce47583e3..d738c7d10b 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationArgumentEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationArgumentEntry.java @@ -15,7 +15,9 @@ */ package org.thingsboard.server.service.cf.ctx.state.aggregation.single; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.script.api.tbel.TbelCfArg; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntryType; @@ -66,6 +68,11 @@ public class EntityAggregationArgumentEntry implements ArgumentEntry { return aggIntervals.isEmpty(); } + @Override + public JsonNode jsonValue() { + return JacksonUtil.valueToTree(aggIntervals); + } + @Override public TbelCfArg toTbelCfArg() { return null; diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java index af7912da46..54f094c289 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java @@ -68,11 +68,19 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt intervalDuration = configuration.getInterval().getIntervalDurationMillis(); Watermark watermark = configuration.getWatermark(); watermarkDuration = watermark == null ? 0 : TimeUnit.SECONDS.toMillis(watermark.getDuration()); - checkInterval = TimeUnit.SECONDS.toMillis(ctx.getAggCheckInterval()); + checkInterval = TimeUnit.SECONDS.toMillis(ctx.getCfCheckInterval()); interval = configuration.getInterval(); metrics = configuration.getMetrics(); } + @Override + public void init(boolean restored) { + super.init(restored); + if (restored) { + fillMissingIntervals(); + } + } + @Override public CalculatedFieldType getType() { return CalculatedFieldType.ENTITY_AGGREGATION; @@ -114,9 +122,7 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt AggIntervalEntry currentInterval = new AggIntervalEntry(interval.getCurrentIntervalStartTs(), interval.getCurrentIntervalEndTs()); arguments.forEach((argName, argumentEntry) -> { var entityAggEntry = (EntityAggregationArgumentEntry) argumentEntry; - if (!entityAggEntry.getAggIntervals().containsKey(currentInterval)) { - entityAggEntry.getAggIntervals().computeIfAbsent(currentInterval, current -> new AggIntervalEntryStatus()); - } + entityAggEntry.getAggIntervals().computeIfAbsent(currentInterval, current -> new AggIntervalEntryStatus()); }); } @@ -244,7 +250,7 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt } ObjectNode resultNode = JacksonUtil.newObjectNode(); if (!metricsNode.isEmpty()) { - resultNode.put("ts", interval.getEndTs()); + resultNode.put("ts", interval.getEndTs() - 1); resultNode.set("values", metricsNode); } result.add(resultNode); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java index b96fde9898..83e08ea67f 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java @@ -122,8 +122,11 @@ public class AlarmCalculatedFieldState extends BaseCalculatedFieldState { } @Override - public void init() { - super.init(); + public void init(boolean restored) { + super.init(restored); + if (restored) { + return; + } AtomicBoolean reevalNeeded = new AtomicBoolean(false); Map createRules = configuration.getCreateRules(); for (AlarmSeverity severity : AlarmSeverity.values()) { diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 1c32d62f6c..cdeb55b7b7 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -531,6 +531,9 @@ actors: calculation_timeout: "${ACTORS_CALCULATION_TIMEOUT_SEC:5}" # Interval in seconds to re-evaluate calculated fields that have a time schedule. 1 minute by default. check_interval: "${ACTORS_CALCULATED_FIELDS_CHECK_INTERVAL_SEC:60}" + alarms: + # Interval in seconds to re-evaluate Alarm rules that have a time schedule. 2 minutes by default. + reevaluation_interval: "${ACTORS_ALARMS_REEVALUATION_INTERVAL_SEC:120}" debug: settings: diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java index d144fe4dcc..cc2d9c8437 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java @@ -105,7 +105,7 @@ public class GeofencingCalculatedFieldStateTest { ctx.init(); state = new GeofencingCalculatedFieldState(ctx.getEntityId()); state.setCtx(ctx, null); - state.init(); + state.init(false); } @Test diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java index ddb9f378b0..88cc6972b8 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java @@ -105,7 +105,7 @@ public class PropagationCalculatedFieldStateTest { state = new PropagationCalculatedFieldState(ctx.getEntityId()); state.setCtx(ctx, null); - state.init(); + state.init(false); } @Test diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldStateTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldStateTest.java index 51e633a232..9691f4a02d 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldStateTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldStateTest.java @@ -88,7 +88,7 @@ public class ScriptCalculatedFieldStateTest { ctx.init(); state = new ScriptCalculatedFieldState(ctx.getEntityId()); state.setCtx(ctx, null); - state.init(); + state.init(false); } @Test diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldStateTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldStateTest.java index a64f1e4c60..6b25643cdf 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldStateTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldStateTest.java @@ -80,7 +80,7 @@ public class SimpleCalculatedFieldStateTest { ctx.init(); state = new SimpleCalculatedFieldState(ctx.getEntityId()); state.setCtx(ctx, null); - state.init(); + state.init(false); } @Test diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/SystemParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/SystemParams.java index c46b466e90..0fa9b2dd78 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/SystemParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/SystemParams.java @@ -41,6 +41,6 @@ public class SystemParams { int minAllowedScheduledUpdateIntervalInSecForCF; int maxRelationLevelPerCfArgument; long minAllowedDeduplicationIntervalInSecForCF; - long minAggregationIntervalInSecForCF; + long minAllowedAggregationIntervalInSecForCF; TrendzSettings trendzSettings; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/EntityAggregationCalculatedFieldConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/EntityAggregationCalculatedFieldConfiguration.java index 78e74ee64d..eb97538701 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/EntityAggregationCalculatedFieldConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/EntityAggregationCalculatedFieldConfiguration.java @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.cf.configuration.aggregation.single; import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.Data; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Argument; @@ -36,8 +37,13 @@ public class EntityAggregationCalculatedFieldConfiguration implements ArgumentsB @Valid @NotEmpty private Map metrics; + @Valid + @NotNull private AggInterval interval; + @Valid private Watermark watermark; + @Valid + @NotNull private Output output; @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/BaseAggInterval.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/BaseAggInterval.java index 71ccab5f95..f7cae9cd9e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/BaseAggInterval.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/BaseAggInterval.java @@ -17,6 +17,9 @@ package org.thingsboard.server.common.data.cf.configuration.aggregation.single.i import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Data; import java.time.DayOfWeek; @@ -32,6 +35,7 @@ import java.time.temporal.TemporalAdjusters; @JsonInclude(JsonInclude.Include.NON_NULL) public abstract class BaseAggInterval implements AggInterval { + @NotBlank protected String tz; protected Long offsetSec; // delay seconds since start of interval diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/Watermark.java b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/Watermark.java index 3b11681982..b07e6a3012 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/Watermark.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/single/interval/Watermark.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.cf.configuration.aggregation.single.interval; +import jakarta.validation.constraints.Min; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -24,6 +25,7 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class Watermark { + @Min(0) private long duration; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index 62af00300e..7587f563ab 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -189,7 +189,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura @Schema(example = "60") private long minAllowedDeduplicationIntervalInSecForCF = 60; @Schema(example = "60") - private long minAggregationIntervalInSecForCF = 60; + private long minAllowedAggregationIntervalInSecForCF = 60; @Override public long getProfileThreshold(ApiUsageRecordKey key) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidator.java index 17d980488d..fa688a0a9e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidator.java @@ -123,10 +123,10 @@ public class CalculatedFieldDataValidator extends DataValidator if (!(calculatedField.getConfiguration() instanceof RelatedEntitiesAggregationCalculatedFieldConfiguration aggConfiguration)) { return; } - long minAllowedDeduplicationInterval = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMinAllowedDeduplicationIntervalInSecForCF); - if (aggConfiguration.getDeduplicationIntervalInSec() < minAllowedDeduplicationInterval) { + long minDeduplicationInterval = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMinAllowedDeduplicationIntervalInSecForCF); + if (aggConfiguration.getDeduplicationIntervalInSec() < minDeduplicationInterval) { throw new IllegalArgumentException("Deduplication interval is less than configured " + - "minimum allowed interval in tenant profile: " + minAllowedDeduplicationInterval); + "minimum allowed interval in tenant profile: " + minDeduplicationInterval); } } @@ -134,7 +134,7 @@ public class CalculatedFieldDataValidator extends DataValidator if (!(calculatedField.getConfiguration() instanceof EntityAggregationCalculatedFieldConfiguration aggConfiguration)) { return; } - long minAggregationIntervalInSec = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMinAggregationIntervalInSecForCF); + long minAggregationIntervalInSec = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMinAllowedAggregationIntervalInSecForCF); if (minAggregationIntervalInSec <= 0) { return; } diff --git a/ui-ngx/src/app/core/auth/auth.models.ts b/ui-ngx/src/app/core/auth/auth.models.ts index 582a407841..21759fbca0 100644 --- a/ui-ngx/src/app/core/auth/auth.models.ts +++ b/ui-ngx/src/app/core/auth/auth.models.ts @@ -32,7 +32,7 @@ export interface SysParamsState { maxDataPointsPerRollingArg: number; maxArgumentsPerCF: number; minAllowedDeduplicationIntervalInSecForCF: number; - minAggregationIntervalInSecForCF: number; + minAllowedAggregationIntervalInSecForCF: number; minAllowedScheduledUpdateIntervalInSecForCF: number; maxRelationLevelPerCfArgument: number; ruleChainDebugPerTenantLimitsConfiguration?: string; diff --git a/ui-ngx/src/app/core/auth/auth.reducer.ts b/ui-ngx/src/app/core/auth/auth.reducer.ts index 8f8258d7b9..af040a6d53 100644 --- a/ui-ngx/src/app/core/auth/auth.reducer.ts +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -34,7 +34,7 @@ const emptyUserAuthState: AuthPayload = { maxResourceSize: 0, maxArgumentsPerCF: 0, minAllowedDeduplicationIntervalInSecForCF: 0, - minAggregationIntervalInSecForCF: 0, + minAllowedAggregationIntervalInSecForCF: 0, minAllowedScheduledUpdateIntervalInSecForCF: 0, maxRelationLevelPerCfArgument: 0, maxDataPointsPerRollingArg: 0, diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html index 2907472143..e4a40c3cdd 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.html @@ -62,14 +62,14 @@ @if (entityAggregationConfiguration.get('interval.type').value === AggIntervalType.CUSTOM) { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts index 8da5c47606..ae63d0f7a5 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts @@ -85,7 +85,7 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor @Input({required: true}) entityName: string; - readonly minAggregationIntervalInSecForCF = getCurrentAuthState(this.store).minAggregationIntervalInSecForCF; + readonly minAllowedAggregationIntervalInSecForCF = getCurrentAuthState(this.store).minAllowedAggregationIntervalInSecForCF; readonly DayInSec = DAY / SECOND; entityAggregationConfiguration = this.fb.group({ @@ -94,9 +94,9 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor interval: this.fb.group({ type: [AggIntervalType.HOUR], tz: ['', Validators.required], - durationSec: [this.minAggregationIntervalInSecForCF, Validators.required], + durationSec: [this.minAllowedAggregationIntervalInSecForCF, Validators.required], allowOffsetSec: [false], - offsetSec: [this.minAggregationIntervalInSecForCF > 60 ? MINUTE / SECOND : 1, Validators.required], + offsetSec: [this.minAllowedAggregationIntervalInSecForCF > 60 ? MINUTE / SECOND : 1, Validators.required], }), allowWatermark: [false], watermark: this.fb.group({ diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 2c2f7b5973..4c311333ec 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -344,12 +344,12 @@ tenant-profile.min-allowed-aggregation-interval - + {{ 'tenant-profile.min-allowed-aggregation-interval-required' | translate}} - + {{ 'tenant-profile.min-allowed-aggregation-interval-range' | translate}} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts index 16997d18a7..a61a1aa1f8 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -117,7 +117,7 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA maxArgumentsPerCF: [0, [Validators.required, Validators.min(0)]], maxRelationLevelPerCfArgument: [1, [Validators.required, Validators.min(1)]], minAllowedDeduplicationIntervalInSecForCF: [0, [Validators.required, Validators.min(0)]], - minAggregationIntervalInSecForCF: [0, [Validators.required, Validators.min(0)]], + minAllowedAggregationIntervalInSecForCF: [0, [Validators.required, Validators.min(0)]], maxRelatedEntitiesToReturnPerCfArgument: [1, [Validators.required, Validators.min(1)]], minAllowedScheduledUpdateIntervalInSecForCF: [0, [Validators.required, Validators.min(0)]], maxDataPointsPerRollingArg: [0, [Validators.required, Validators.min(0)]], diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index 7a96051239..0cfa8df888 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -108,7 +108,7 @@ export interface DefaultTenantProfileConfiguration { maxArgumentsPerCF: number; maxRelationLevelPerCfArgument: number; minAllowedDeduplicationIntervalInSecForCF: number; - minAggregationIntervalInSecForCF: number; + minAllowedAggregationIntervalInSecForCF: number; maxRelatedEntitiesToReturnPerCfArgument: number; minAllowedScheduledUpdateIntervalInSecForCF: number; maxDataPointsPerRollingArg: number; @@ -177,7 +177,7 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan maxDataPointsPerRollingArg: 1000, maxRelationLevelPerCfArgument: 10, minAllowedDeduplicationIntervalInSecForCF: 60, - minAggregationIntervalInSecForCF: 60, + minAllowedAggregationIntervalInSecForCF: 60, maxRelatedEntitiesToReturnPerCfArgument: 100, minAllowedScheduledUpdateIntervalInSecForCF: 0, maxStateSizeInKBytes: 32,