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 4e6711b150..e6dc7cbcd3 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 @@ -262,17 +262,35 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware } private void onTenantProfileUpdated(ComponentLifecycleMsg msg, TbCallback callback) { + checkCfIntervalForUpdate(); + + long maxRelatedEntitiesPerCfArgument = systemContext.getApiLimitService().getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxRelatedEntitiesToReturnPerCfArgument); + List cfsToReinit = new ArrayList<>(); + Stream.concat( + calculatedFields.values().stream(), + entityIdCalculatedFields.values().stream().flatMap(Collection::stream) + ).forEach(ctx -> { + if (ctx.hasRelatedEntities() && ctx.getMaxRelatedEntitiesPerCfArgument() != maxRelatedEntitiesPerCfArgument) { + cfsToReinit.add(ctx); + } + ctx.setTenantProfileProperties(); + }); + + if (!cfsToReinit.isEmpty()) { + MultipleTbCallback cfsReinitCallback = new MultipleTbCallback(cfsToReinit.size(), callback); + cfsToReinit.forEach(ctx -> applyToTargetCfEntityActors(ctx, cfsReinitCallback, (id, cb) -> initCfForEntity(id, ctx, StateAction.REINIT, cb))); + } else { + callback.onSuccess(); + } + } + + private void checkCfIntervalForUpdate() { long updatedCfCheckInterval = systemContext.getApiLimitService().getLimit(tenantId, DefaultTenantProfileConfiguration::getCfReevaluationCheckInterval); if (cfCheckInterval != updatedCfCheckInterval) { cfCheckInterval = updatedCfCheckInterval; cancelReevaluationTask(); scheduleCfsReevaluation(); } - Stream.concat( - calculatedFields.values().stream(), - entityIdCalculatedFields.values().stream().flatMap(Collection::stream) - ).forEach(CalculatedFieldCtx::setTenantProfileProperties); - callback.onSuccess(); } private void onEntityCreated(ComponentLifecycleMsg msg, TbCallback callback) { @@ -383,10 +401,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware .toList(); MultipleTbCallback directionCallback = new MultipleTbCallback(matchingCfs.size(), parentCallback); - - matchingCfs.forEach(ctx -> - applyToTargetCfEntityActors(ctx, directionCallback, (entityId, cb) -> relationAction.accept(entityId, ctx, cb)) - ); + matchingCfs.forEach(ctx -> relationAction.accept(mainId, ctx, directionCallback)); } private void onCfCreated(ComponentLifecycleMsg msg, TbCallback callback) throws CalculatedFieldException { @@ -580,18 +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 -> { - 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/controller/SystemInfoController.java b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java index bca55e9784..ee2e871d6e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java +++ b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java @@ -164,6 +164,7 @@ public class SystemInfoController extends BaseController { systemParams.setMaxDataPointsPerRollingArg(tenantProfileConfiguration.getMaxDataPointsPerRollingArg()); systemParams.setMinAllowedScheduledUpdateIntervalInSecForCF(tenantProfileConfiguration.getMinAllowedScheduledUpdateIntervalInSecForCF()); systemParams.setMaxRelationLevelPerCfArgument(tenantProfileConfiguration.getMaxRelationLevelPerCfArgument()); + systemParams.setMaxRelatedEntitiesToReturnPerCfArgument(tenantProfileConfiguration.getMaxRelatedEntitiesToReturnPerCfArgument()); systemParams.setMinAllowedDeduplicationIntervalInSecForCF(tenantProfileConfiguration.getMinAllowedDeduplicationIntervalInSecForCF()); systemParams.setMinAllowedAggregationIntervalInSecForCF(tenantProfileConfiguration.getMinAllowedAggregationIntervalInSecForCF()); systemParams.setIntermediateAggregationIntervalInSecForCF(tenantProfileConfiguration.getIntermediateAggregationIntervalInSecForCF()); diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index e46f31b32a..efa1d36cff 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -169,7 +169,7 @@ public class TenantController extends BaseController { return checkNotNull(tenantService.findTenantInfos(pageLink)); } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @GetMapping(value = "/tenants", params = {"tenantIds"}) public List getTenantsByIds( @Parameter(description = "A list of tenant ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string"))) @@ -179,7 +179,18 @@ public class TenantController extends BaseController { for (UUID tenantIdUUID : tenantUUIDs) { tenantIds.add(TenantId.fromUUID(tenantIdUUID)); } - return tenantService.findTenantsByIds(tenantId, tenantIds); + List tenants = tenantService.findTenantsByIds(tenantId, tenantIds); + return filterTenantsByReadPermission(tenants); + } + + private List filterTenantsByReadPermission(List tenants) { + return tenants.stream().filter(tenant -> { + try { + return accessControlService.hasPermission(getCurrentUser(), Resource.TENANT, Operation.READ, tenant.getId(), tenant); + } catch (ThingsboardException e) { + return false; + } + }).toList(); } } 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 fed1e82e96..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,14 +247,8 @@ public abstract class AbstractCalculatedFieldProcessingService { } return switch (relation.direction()) { - case FROM -> relations.stream() - .map(EntityRelation::getTo) - .toList(); - case TO -> 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/CalculatedFieldCtx.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java index 6500bfb892..e5215eca28 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 @@ -29,6 +29,7 @@ import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.actors.calculatedField.CalculatedFieldReevaluateMsg; import org.thingsboard.server.common.data.AttributeScope; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.alarm.rule.AlarmRule; import org.thingsboard.server.common.data.alarm.rule.condition.expression.TbelAlarmConditionExpression; import org.thingsboard.server.common.data.cf.CalculatedField; @@ -54,11 +55,9 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.util.CollectionsUtil; import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.dao.usagerecord.ApiLimitService; import org.thingsboard.server.dao.util.TimeUtils; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto; import org.thingsboard.server.service.cf.CalculatedFieldProcessingService; @@ -129,6 +128,7 @@ public class CalculatedFieldCtx implements Closeable { private long scheduledUpdateIntervalMillis; private long cfCheckReevaluationIntervalMillis; private long alarmReevaluationIntervalMillis; + private long maxRelatedEntitiesPerCfArgument; private Argument propagationArgument; private boolean applyExpressionForResolvedArguments; @@ -301,12 +301,18 @@ public class CalculatedFieldCtx implements Closeable { } public void setTenantProfileProperties() { - ApiLimitService apiLimitService = systemContext.getApiLimitService(); - this.maxStateSize = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxStateSizeInKBytes) * 1024; - this.maxSingleValueArgumentSize = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxSingleValueArgumentSizeInKBytes) * 1024; - this.intermediateAggregationIntervalMillis = TimeUnit.SECONDS.toMillis(apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getIntermediateAggregationIntervalInSecForCF)); - this.cfCheckReevaluationIntervalMillis = TimeUnit.SECONDS.toMillis(apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getCfReevaluationCheckInterval)); - this.alarmReevaluationIntervalMillis = TimeUnit.SECONDS.toMillis(apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getAlarmsReevaluationInterval)); + TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenantId); + if (tenantProfile == null) { + throw new IllegalStateException("Tenant Profile not found for tenant: " + tenantId); + } + tenantProfile.getProfileConfiguration().ifPresent(config -> { + this.maxStateSize = config.getMaxStateSizeInKBytes() * 1024L; + this.maxSingleValueArgumentSize = config.getMaxSingleValueArgumentSizeInKBytes() * 1024L; + this.intermediateAggregationIntervalMillis = TimeUnit.SECONDS.toMillis(config.getIntermediateAggregationIntervalInSecForCF()); + this.cfCheckReevaluationIntervalMillis = TimeUnit.SECONDS.toMillis(config.getCfReevaluationCheckInterval()); + this.alarmReevaluationIntervalMillis = TimeUnit.SECONDS.toMillis(config.getAlarmsReevaluationInterval()); + this.maxRelatedEntitiesPerCfArgument = config.getMaxRelatedEntitiesToReturnPerCfArgument(); + }); } public double evaluateSimpleExpression(Expression expression, CalculatedFieldState state) { @@ -756,6 +762,12 @@ public class CalculatedFieldCtx implements Closeable { return scheduledUpdateIntervalMillis == DISABLED_INTERVAL_VALUE; } + public boolean hasRelatedEntities() { + return CalculatedFieldType.GEOFENCING == cfType + || CalculatedFieldType.PROPAGATION == cfType + || CalculatedFieldType.RELATED_ENTITIES_AGGREGATION == cfType; + } + public boolean shouldFetchRelatedEntities(CalculatedFieldState state) { if (!cfHasRelationPathQuerySource) { return false; 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 1939318e7a..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,23 +66,34 @@ public class RelatedEntitiesArgumentEntry implements ArgumentEntry, HasLatestTs @Override public boolean updateEntry(ArgumentEntry entry, CalculatedFieldCtx ctx) { if (entry instanceof RelatedEntitiesArgumentEntry relatedEntitiesArgumentEntry) { + checkRelatedEntitiesNumber(ctx); entityInputs.putAll(relatedEntitiesArgumentEntry.entityInputs); - return true; } else if (entry instanceof SingleValueArgumentEntry singleValueArgumentEntry) { if (entry.isForceResetPrevious()) { + checkRelatedEntitiesNumber(ctx); entityInputs.put(singleValueArgumentEntry.getEntityId(), singleValueArgumentEntry); - return true; - } - ArgumentEntry argumentEntry = entityInputs.get(singleValueArgumentEntry.getEntityId()); - if (argumentEntry != null) { - argumentEntry.updateEntry(singleValueArgumentEntry, ctx); } else { - 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 checkRelatedEntitiesNumber(CalculatedFieldCtx ctx) { + if (entityInputs.size() >= ctx.getMaxRelatedEntitiesPerCfArgument()) { + throw new IllegalArgumentException( + "Exceeded the maximum allowed related entities per argument '" + + ctx.getMaxRelatedEntitiesPerCfArgument() + "'. Increase the limit in the tenant profile configuration." + ); + } } @Override 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 0929eb7ea0..1297a22478 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,6 +68,8 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt private CalculatedFieldProcessingService cfProcessingService; + private long now; + public EntityAggregationCalculatedFieldState(EntityId entityId) { super(entityId); } @@ -100,7 +102,7 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt @Override public ListenableFuture performCalculation(Map updatedArgs, CalculatedFieldCtx ctx) throws Exception { createIntervalIfNotExist(); - long now = System.currentTimeMillis(); + now = System.currentTimeMillis(); if (DebugModeUtil.isDebugFailuresAvailable(ctx.getCalculatedField())) { if (debugTracker == null) { @@ -114,7 +116,7 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt Map> results = new HashMap<>(); List expiredIntervals = new ArrayList<>(); getIntervals().forEach((intervalEntry, argIntervalStatuses) -> { - processInterval(now, intervalEntry, argIntervalStatuses, expiredIntervals, results); + processInterval(intervalEntry, argIntervalStatuses, expiredIntervals, results); }); removeExpiredIntervals(expiredIntervals); @@ -202,8 +204,7 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt return intervals; } - private void processInterval(long now, - AggIntervalEntry intervalEntry, + private void processInterval(AggIntervalEntry intervalEntry, Map args, List expiredIntervals, Map> results) { @@ -244,11 +245,11 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt args.forEach((argName, argEntryIntervalStatus) -> { if (argEntryIntervalStatus.intervalPassed(cfCheckInterval)) { if (argEntryIntervalStatus.argsUpdated()) { - argEntryIntervalStatus.setLastMetricsEvalTs(System.currentTimeMillis()); + argEntryIntervalStatus.setLastMetricsEvalTs(now); argEntryIntervalStatus.setLastArgsRefreshTs(DEFAULT_LAST_UPDATE_TS); processArgument(intervalEntry, argName, false, results); } else if (argEntryIntervalStatus.getLastMetricsEvalTs() == DEFAULT_LAST_UPDATE_TS) { - argEntryIntervalStatus.setLastMetricsEvalTs(System.currentTimeMillis()); + argEntryIntervalStatus.setLastMetricsEvalTs(now); processArgument(intervalEntry, argName, true, results); } } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java index 7b8b393371..c3b5951da6 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java @@ -65,7 +65,7 @@ public class PropagationArgumentEntry implements ArgumentEntry { throw new IllegalArgumentException("Unsupported argument entry type for propagation argument entry: " + entry.getType()); } if (updated.getAdded() != null) { - return checkAdded(updated.getAdded()); + return checkAdded(updated.getAdded(), ctx); } if (updated.getRemoved() != null) { return entityIds.remove(updated.getRemoved()); @@ -80,7 +80,7 @@ public class PropagationArgumentEntry implements ArgumentEntry { return true; } boolean retained = entityIds.retainAll(dbEntityIds); - boolean added = checkAdded(dbEntityIds); + boolean added = checkAdded(dbEntityIds, ctx); return retained || added; } if (updated.isEmpty()) { @@ -91,8 +91,14 @@ public class PropagationArgumentEntry implements ArgumentEntry { return true; } - private boolean checkAdded(Collection updatedIds) { + private boolean checkAdded(Collection updatedIds, CalculatedFieldCtx ctx) { for (EntityId id : updatedIds) { + if (entityIds.size() >= ctx.getMaxRelatedEntitiesPerCfArgument()) { + throw new IllegalArgumentException( + "Exceeded the maximum allowed related entities per argument '" + + ctx.getMaxRelatedEntitiesPerCfArgument() + "'. Increase the limit in the tenant profile configuration." + ); + } if (entityIds.add(id)) { if (added == null) { added = new ArrayList<>(); diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EventsDeletionTaskProcessor.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EventsDeletionTaskProcessor.java index 596057ca68..5917c080a7 100644 --- a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EventsDeletionTaskProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EventsDeletionTaskProcessor.java @@ -17,10 +17,13 @@ package org.thingsboard.server.service.housekeeper.processor; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.event.EventType; import org.thingsboard.server.common.data.housekeeper.HousekeeperTask; import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; import org.thingsboard.server.dao.event.EventService; +import java.util.Arrays; + @Component @RequiredArgsConstructor public class EventsDeletionTaskProcessor extends HousekeeperTaskProcessor { @@ -29,7 +32,9 @@ public class EventsDeletionTaskProcessor extends HousekeeperTaskProcessor !eventType.isDebug()).toArray(EventType[]::new); + eventService.removeEvents(task.getTenantId(), task.getEntityId(), 0L, System.currentTimeMillis(), nonDebugEventTypes); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java index c5bf884b6c..79685bc544 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java @@ -20,7 +20,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import org.thingsboard.server.dao.edge.EdgeEventDao; import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; import org.thingsboard.server.queue.discovery.PartitionService; 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/cf/RelatedEntitiesAggregationCalculatedFieldTest.java b/application/src/test/java/org/thingsboard/server/cf/RelatedEntitiesAggregationCalculatedFieldTest.java index 10a7b46282..4c708bce56 100644 --- a/application/src/test/java/org/thingsboard/server/cf/RelatedEntitiesAggregationCalculatedFieldTest.java +++ b/application/src/test/java/org/thingsboard/server/cf/RelatedEntitiesAggregationCalculatedFieldTest.java @@ -470,21 +470,47 @@ public class RelatedEntitiesAggregationCalculatedFieldTest extends AbstractContr @Test public void testCreateRelation_checkAggregation() throws Exception { - createOccupancyCF(asset.getId()); - checkInitialCalculation(); + Asset asset2 = createAsset("Asset 2", assetProfile.getId()); + Device device3 = createDevice("Device 3", "1234567890333"); + Device device4 = createDevice("Device 4", "1234567890444"); - Device device3 = createDevice("Device 3", deviceProfile.getId(), "1234567890333"); + createEntityRelation(asset2.getId(), device3.getId(), "Contains"); + createEntityRelation(asset2.getId(), device4.getId(), "Contains"); - postTelemetry(device3.getId(), "{\"occupied\":true}"); + createOccupancyCF(assetProfile.getId()); - createEntityRelation(asset.getId(), device3.getId(), "Contains"); + await().alias("create CF and perform initial aggregation").atMost(TIMEOUT, TimeUnit.SECONDS) + .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) + .untilAsserted(() -> { + verifyTelemetry(asset.getId(), Map.of( + "freeSpaces", "1", + "occupiedSpaces", "1", + "totalSpaces", "2" + )); - await().alias("create relation and perform aggregation").atMost(deduplicationInterval * 2, TimeUnit.SECONDS) + verifyTelemetry(asset2.getId(), Map.of( + "freeSpaces", "2", + "occupiedSpaces", "0", + "totalSpaces", "2" + )); + }); + + Device device5 = createDevice("Device 5", "1234567890555"); + createEntityRelation(asset2.getId(), device5.getId(), "Contains"); + + await().alias("create relation and perform aggregation on asset 2") + .atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .untilAsserted(() -> { verifyTelemetry(asset.getId(), Map.of( "freeSpaces", "1", - "occupiedSpaces", "2", + "occupiedSpaces", "1", + "totalSpaces", "2" + )); + + verifyTelemetry(asset2.getId(), Map.of( + "freeSpaces", "3", + "occupiedSpaces", "0", "totalSpaces", "3" )); }); @@ -721,6 +747,43 @@ public class RelatedEntitiesAggregationCalculatedFieldTest extends AbstractContr }); } + @Test + public void testUpdateMaxRelatedEntitiesPerArgument_checkAggregation() throws Exception { + loginSysAdmin(); + + updateDefaultTenantProfileConfig(tenantProfileConfig -> { + tenantProfileConfig.setMaxRelatedEntitiesToReturnPerCfArgument(1); + }); + + login("tenant@thingsboard.org", "testPassword"); + + createCountCF(asset.getId()); + + await().alias("create CF and perform initial aggregation").atMost(deduplicationInterval * 2, TimeUnit.SECONDS) + .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) + .untilAsserted(() -> { + ObjectNode numberOfDevices = getLatestTelemetry(asset.getId(), "numberOfDevices"); + assertThat(numberOfDevices).isNotNull(); + assertThat(numberOfDevices.get("numberOfDevices").get(0).get("value").asText()).isEqualTo("1"); + }); + + loginSysAdmin(); + + updateDefaultTenantProfileConfig(tenantProfileConfig -> { + tenantProfileConfig.setMaxRelatedEntitiesToReturnPerCfArgument(10); + }); + + login("tenant@thingsboard.org", "testPassword"); + + await().alias("update max related entities per argument and perform initial aggregation").atMost(deduplicationInterval * 2, TimeUnit.SECONDS) + .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) + .untilAsserted(() -> { + ObjectNode numberOfDevices = getLatestTelemetry(asset.getId(), "numberOfDevices"); + assertThat(numberOfDevices).isNotNull(); + assertThat(numberOfDevices.get("numberOfDevices").get(0).get("value").asText()).isEqualTo("2"); + }); + } + private void checkInitialCalculation() { await().alias("create CF and perform initial aggregation").atMost(deduplicationInterval * 2, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) @@ -832,6 +895,30 @@ public class RelatedEntitiesAggregationCalculatedFieldTest extends AbstractContr output); } + private CalculatedField createCountCF(EntityId entityId) { + Map arguments = new HashMap<>(); + Argument argument = new Argument(); + argument.setRefEntityKey(new ReferencedEntityKey("active", ArgumentType.TS_LATEST, null)); + argument.setDefaultValue("true"); + arguments.put("active", argument); + + Map aggMetrics = new HashMap<>(); + + AggMetric avgMetric = new AggMetric(); + avgMetric.setFunction(AggFunction.COUNT); + avgMetric.setInput(new AggKeyInput("active")); + aggMetrics.put("numberOfDevices", avgMetric); + + TimeSeriesOutput output = new TimeSeriesOutput(); + output.setDecimalsByDefault(0); + + return createAggCf("Number of devices", entityId, + new RelationPathLevel(EntitySearchDirection.FROM, "Contains"), + arguments, + aggMetrics, + output); + } + private CalculatedField createAggCf(String name, EntityId entityId, RelationPathLevel relation, 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/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java index 517a0f69ce..fb86ea7b43 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java @@ -219,6 +219,15 @@ public class TenantControllerTest extends AbstractControllerTest { Assert.assertEquals(savedTenant, foundTenant); } + loginTenantAdmin(); + Tenant[] foundTenantsByTenant = doGet("/api/tenants?tenantIds=" + idsParam, Tenant[].class); + assertThat(foundTenantsByTenant).isEmpty(); + + Tenant[] foundCurrentTenant = doGet("/api/tenants?tenantIds=" + tenantId, Tenant[].class); + assertThat(foundCurrentTenant).hasSize(1); + assertThat(foundCurrentTenant[0].getTenantId()).isEqualTo(tenantId); + + loginSysAdmin(); for (Tenant savedTenant : savedTenants) { deleteTenant(savedTenant.getId()); } diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java index 596720d213..7db8bc658a 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java @@ -33,6 +33,7 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.lenient; @ExtendWith(MockitoExtension.class) public class PropagationArgumentEntryTest { @@ -48,6 +49,8 @@ public class PropagationArgumentEntryTest { @BeforeEach void setUp() { + lenient().when(ctx.getMaxRelatedEntitiesPerCfArgument()).thenReturn(1000L); + List propagationEntityIds = new ArrayList<>(); propagationEntityIds.add(ENTITY_1_ID); propagationEntityIds.add(ENTITY_2_ID); diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesArgumentEntryTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesArgumentEntryTest.java index db8ce32df6..725860dd80 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesArgumentEntryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesArgumentEntryTest.java @@ -32,6 +32,7 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.lenient; @ExtendWith(MockitoExtension.class) public class RelatedEntitiesArgumentEntryTest { @@ -48,6 +49,8 @@ public class RelatedEntitiesArgumentEntryTest { @BeforeEach void setUp() { + lenient().when(ctx.getMaxRelatedEntitiesPerCfArgument()).thenReturn(1000L); + Map aggInputs = new HashMap<>(); aggInputs.put(device1, new SingleValueArgumentEntry(device1, new BasicTsKvEntry(ts - 100, new LongDataEntry("key", 12L), 1L))); aggInputs.put(device2, new SingleValueArgumentEntry(device2, new BasicTsKvEntry(ts - 150, new LongDataEntry("key", 16L), 6L))); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java index adfe8a2435..2d865542a1 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.notification; import com.fasterxml.jackson.core.type.TypeReference; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -30,7 +31,12 @@ import org.thingsboard.server.common.data.notification.targets.NotificationTarge import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter; import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter; +import org.thingsboard.server.common.data.notification.targets.platform.UsersFilterType; +import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation; +import org.thingsboard.server.common.data.notification.targets.slack.SlackConversationType; +import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.notification.NotificationTargetDao; @@ -175,6 +181,28 @@ public class NotificationTargetApiTest extends AbstractNotificationApiTest { assertThat(error).containsIgnoringCase("referenced by scheduled notification request"); } + @Test + public void findByTenantIdAndUsersFilterType() throws Exception { + loginSysAdmin(); + NotificationTarget sysAdmins = createNotificationTarget(new SystemAdministratorsFilter()); + + NotificationTarget slack = new NotificationTarget(); + slack.setName(RandomStringUtils.randomNumeric(5)); + SlackNotificationTargetConfig slackConfig = new SlackNotificationTargetConfig(); + SlackConversation slackConversation = new SlackConversation(); + slackConversation.setType(SlackConversationType.DIRECT); + slackConversation.setId("U12345678"); + slackConversation.setName("test"); + slackConfig.setConversation(slackConversation); + slack.setConfiguration(slackConfig); + slack = saveNotificationTarget(slack); + + List sysAdminTargets = notificationTargetDao.findByTenantIdAndUsersFilterType( + TenantId.SYS_TENANT_ID, UsersFilterType.SYSTEM_ADMINISTRATORS + ); + assertThat(sysAdminTargets).containsOnly(sysAdmins); + } + private String saveAndGetError(NotificationTarget notificationTarget, ResultMatcher statusMatcher) throws Exception { return getErrorMessage(save(notificationTarget, statusMatcher)); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java index 92088f0781..a3280d0053 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java @@ -41,6 +41,8 @@ public interface EventService { void removeEvents(TenantId tenantId, EntityId entityId); + void removeEvents(TenantId tenantId, EntityId entityId, Long startTime, Long endTime, EventType... types); + void removeEvents(TenantId tenantId, EntityId entityId, EventFilter eventFilter, Long startTime, Long endTime); void cleanupEvents(long regularEventExpTs, long debugEventExpTs, boolean cleanupDb); 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 678fdc0900..3cd2ada38f 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 @@ -40,6 +40,7 @@ public class SystemParams { long maxDataPointsPerRollingArg; int minAllowedScheduledUpdateIntervalInSecForCF; int maxRelationLevelPerCfArgument; + int maxRelatedEntitiesToReturnPerCfArgument; long minAllowedDeduplicationIntervalInSecForCF; long minAllowedAggregationIntervalInSecForCF; long intermediateAggregationIntervalInSecForCF; 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/event/BaseEventService.java b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java index 270bef1614..b98a82cac6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java @@ -47,11 +47,6 @@ import java.util.stream.Collectors; @Slf4j public class BaseEventService implements EventService { - @Value("${sql.ttl.events.events_ttl:0}") - private long ttlInSec; - @Value("${sql.ttl.events.debug_events_ttl:604800}") - private long debugTtlInSec; - @Value("${event.debug.max-symbols:4096}") private int maxDebugEventSymbols; @@ -125,7 +120,12 @@ public class BaseEventService implements EventService { @Override public void removeEvents(TenantId tenantId, EntityId entityId) { - removeEvents(tenantId, entityId, null, null, null); + removeEvents(tenantId, entityId, null, null); + } + + @Override + public void removeEvents(TenantId tenantId, EntityId entityId, Long startTime, Long endTime, EventType... types) { + eventDao.removeEvents(tenantId.getId(), entityId.getId(), startTime, endTime, types); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java index 54c8cb2690..12ffc2e00a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java @@ -25,80 +25,24 @@ import org.thingsboard.server.common.data.page.TimePageLink; import java.util.List; import java.util.UUID; -/** - * The Interface EventDao. - */ public interface EventDao { - /** - * Save or update event object async - * - * @param event the event object - * @return saved event object future - */ ListenableFuture saveAsync(Event event); - /** - * Find events by tenantId, entityId, eventType and pageLink. - * - * @param tenantId the tenantId - * @param entityId the entityId - * @param eventType the eventType - * @param pageLink the pageLink - * @return the event list - */ PageData findEvents(UUID tenantId, UUID entityId, EventType eventType, TimePageLink pageLink); PageData findEventByFilter(UUID tenantId, UUID entityId, EventFilter eventFilter, TimePageLink pageLink); - /** - * Find latest events by tenantId, entityId and eventType. - * - * @param tenantId the tenantId - * @param entityId the entityId - * @param eventType the eventType - * @param limit the limit - * @return the event list - */ List findLatestEvents(UUID tenantId, UUID entityId, EventType eventType, int limit); - /** - * Find latest debug IN event by tenantId, entityId. - * - * @param tenantId the tenantId - * @param entityId the entityId - * @return the latest debug IN event - */ Event findLatestDebugRuleNodeInEvent(UUID tenantId, UUID entityId); - /** - * Executes stored procedure to cleanup old events. Uses separate ttl for debug and other events. - * @param regularEventExpTs the expiration time of the regular events - * @param debugEventExpTs the expiration time of the debug events - * @param cleanupDb - */ void cleanupEvents(long regularEventExpTs, long debugEventExpTs, boolean cleanupDb); - /** - * Removes all events for the specified entity and time interval - * - * @param tenantId - * @param entityId - * @param startTime - * @param endTime - */ void removeEvents(UUID tenantId, UUID entityId, Long startTime, Long endTime); - /** - * - * Removes all events for the specified entity, event filter and time interval - * - * @param tenantId - * @param entityId - * @param eventFilter - * @param startTime - * @param endTime - */ + void removeEvents(UUID tenantId, UUID entityId, Long startTime, Long endTime, EventType... types); + void removeEvents(UUID tenantId, UUID entityId, EventFilter eventFilter, Long startTime, Long endTime); } 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 0fd914df7c..6f404e1aca 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,14 +52,15 @@ 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; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -518,7 +519,13 @@ class BaseRelationService implements RelationService { return Collections.emptyList(); } List relations = relationFilter != null ? filterRelations(entityRelations, relationFilter) : entityRelations; - return relations.size() > limit ? relations.subList(0, limit) : relations; + if (relations.size() > limit) { + List limitedRelations = new ArrayList<>(relations); + limitedRelations.sort(Comparator.comparing(r -> r.getFrom().getId())); + return limitedRelations.subList(0, limit); + } else { + return relations; + } }, directExecutor()); } return executor.submit(() -> { @@ -545,7 +552,13 @@ 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; + if (relations.size() > limit) { + List limitedRelations = new ArrayList<>(relations); + limitedRelations.sort(Comparator.comparing(r -> r.getFrom().getId())); + return limitedRelations.subList(0, limit); + } else { + return relations; + } } return relationDao.findByRelationPathQuery(tenantId, relationPathQuery, limit); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java index ba2f3455e0..10243dd959 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java @@ -31,9 +31,6 @@ import org.thingsboard.server.dao.model.sql.AssetInfoEntity; import java.util.List; import java.util.UUID; -/** - * Created by Valerii Sosliuk on 5/21/2017. - */ public interface AssetRepository extends JpaRepository, ExportableEntityRepository { @Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo, p.name) " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java index 3f27797a71..8b52e0024b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java @@ -86,9 +86,6 @@ public class JpaBaseEdgeEventDao extends JpaPartitionedAbstractDao queue; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java index eea4c9760f..fb78aef774 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java @@ -30,9 +30,6 @@ import org.thingsboard.server.dao.model.sql.EntityViewInfoEntity; import java.util.List; import java.util.UUID; -/** - * Created by Victor Basanets on 8/31/2017. - */ public interface EntityViewRepository extends JpaRepository, ExportableEntityRepository { @Query("SELECT new org.thingsboard.server.dao.model.sql.EntityViewInfoEntity(e, c.title, c.additionalInfo) " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java index eb3f9b6a21..3d6fd3f8fe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java @@ -45,9 +45,6 @@ import java.util.UUID; import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityTypesToDto; -/** - * Created by Victor Basanets on 8/31/2017. - */ @Component @Slf4j @SqlDao diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java index e3913cdf1c..91497f3be3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java @@ -173,8 +173,14 @@ public class JpaBaseEventDao implements EventDao { @Override public void removeEvents(UUID tenantId, UUID entityId, Long startTime, Long endTime) { + removeEvents(tenantId, entityId, startTime, endTime, EventType.values()); + } + + @Override + public void removeEvents(UUID tenantId, UUID entityId, Long startTime, Long endTime, EventType... types) { log.debug("[{}][{}] Remove events [{}-{}] ", tenantId, entityId, startTime, endTime); - for (EventType eventType : EventType.values()) { + EventType[] eventTypes = (types == null || types.length == 0) ? EventType.values() : types; + for (EventType eventType : eventTypes) { getEventRepository(eventType).removeEvents(tenantId, entityId, startTime, endTime); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java index d05172e255..00dddd0114 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java @@ -67,7 +67,7 @@ public class JpaNotificationTargetDao extends JpaAbstractDao findByTenantIdAndUsersFilterType(TenantId tenantId, UsersFilterType filterType) { - return DaoUtil.convertDataList(notificationTargetRepository.findByTenantIdAndSearchTextAndUsersFilterTypeIfPresent(tenantId.getId(), null, + return DaoUtil.convertDataList(notificationTargetRepository.findByTenantIdAndUsersFilterType(tenantId.getId(), List.of(filterType.name()), DaoUtil.toPageable(new PageLink(Integer.MAX_VALUE))).getContent()); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java index 961e734c9e..e807a6bffb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java @@ -33,20 +33,27 @@ import java.util.UUID; public interface NotificationTargetRepository extends JpaRepository, ExportableEntityRepository { @Query("SELECT t FROM NotificationTargetEntity t WHERE t.tenantId = :tenantId " + - "AND (:searchText is NULL OR ilike(t.name, concat('%', :searchText, '%')) = true)") + "AND (:searchText is NULL OR ilike(t.name, concat('%', :searchText, '%')) = true)") Page findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, Pageable pageable); @Query(value = "SELECT * FROM notification_target t WHERE t.tenant_id = :tenantId " + - "AND (:searchText IS NULL OR t.name ILIKE concat('%', :searchText, '%')) " + - "AND (cast(t.configuration as json) ->> 'type' <> 'PLATFORM_USERS' OR " + - "cast(t.configuration as json) -> 'usersFilter' ->> 'type' IN :usersFilterTypes)", nativeQuery = true) + "AND (:searchText IS NULL OR t.name ILIKE concat('%', :searchText, '%')) " + + "AND (cast(t.configuration as json) ->> 'type' <> 'PLATFORM_USERS' OR " + + "cast(t.configuration as json) -> 'usersFilter' ->> 'type' IN :usersFilterTypes)", nativeQuery = true) Page findByTenantIdAndSearchTextAndUsersFilterTypeIfPresent(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, @Param("usersFilterTypes") List usersFilterTypes, Pageable pageable); + @Query(value = "SELECT * FROM notification_target t WHERE t.tenant_id = :tenantId " + + "AND (cast(t.configuration as json) ->> 'type' = 'PLATFORM_USERS' AND " + + "cast(t.configuration as json) -> 'usersFilter' ->> 'type' IN :usersFilterTypes)", nativeQuery = true) + Page findByTenantIdAndUsersFilterType(@Param("tenantId") UUID tenantId, + @Param("usersFilterTypes") List usersFilterTypes, + Pageable pageable); + List findByTenantIdAndIdIn(UUID tenantId, List ids); @Transactional diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java index 0ca8acf792..4648f53e19 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java @@ -53,9 +53,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TYPE_GROU import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TYPE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.VERSION_COLUMN; -/** - * Created by Valerii Sosliuk on 5/29/2017. - */ @Slf4j @Component @SqlDao @@ -260,9 +257,7 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple if (!CollectionUtils.isEmpty(relationTypeGroups)) { sqlBuilder.append("AND relation_type_group IN (?"); - for (int i = 1; i < relationTypeGroups.size(); i++) { - sqlBuilder.append(", ?"); - } + sqlBuilder.append(", ?".repeat(Math.max(0, relationTypeGroups.size() - 1))); sqlBuilder.append(")"); params.addAll(relationTypeGroups); } @@ -289,8 +284,7 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple relation.setTypeGroup(RelationTypeGroup.valueOf((String) relationTypeGroup)); relation.setVersion((Long) version); return relation; - }) - .collect(Collectors.toList()); + }).toList(); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java index 282ffab377..3b0eb13a93 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java @@ -28,9 +28,6 @@ import org.thingsboard.server.dao.model.sql.TenantInfoEntity; import java.util.List; import java.util.UUID; -/** - * Created by Valerii Sosliuk on 4/30/2017. - */ public interface TenantRepository extends JpaRepository { @Query("SELECT new org.thingsboard.server.dao.model.sql.TenantInfoEntity(t, p.name) " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java index 0a0369ea19..6f5976745d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java @@ -29,9 +29,6 @@ import org.thingsboard.server.dao.model.sql.ApiUsageStateEntity; import java.util.List; import java.util.UUID; -/** - * @author Valerii Sosliuk - */ public interface ApiUsageStateRepository extends JpaRepository { @Query("SELECT ur FROM ApiUsageStateEntity ur WHERE ur.tenantId = :tenantId " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java index 717ad0e520..722f75045b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java @@ -34,9 +34,6 @@ import org.thingsboard.server.dao.util.SqlDao; import java.util.List; import java.util.UUID; -/** - * @author Andrii Shvaika - */ @Component @SqlDao public class JpaApiUsageStateDao extends JpaAbstractDao implements ApiUsageStateDao { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java index 587b396e53..65e0cc8288 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java @@ -53,9 +53,6 @@ import java.util.stream.Collectors; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; -/** - * Created by Valerii Sosliuk on 4/29/2017. - */ @Component @SqlDao public class JpaWidgetTypeDao extends JpaAbstractDao implements WidgetTypeDao, TenantEntityDao { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java index b4281bc4c2..e1c6341203 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java @@ -42,9 +42,6 @@ import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; -/** - * Created by Valerii Sosliuk on 4/23/2017. - */ @Component @SqlDao public class JpaWidgetsBundleDao extends JpaAbstractDao implements WidgetsBundleDao, TenantEntityDao { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java index fc50393c62..1830c7fefc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java @@ -28,9 +28,6 @@ import org.thingsboard.server.dao.model.sql.WidgetsBundleEntity; import java.util.List; import java.util.UUID; -/** - * Created by Valerii Sosliuk on 4/23/2017. - */ public interface WidgetsBundleRepository extends JpaRepository, ExportableEntityRepository { WidgetsBundleEntity findWidgetsBundleByTenantIdAndAlias(UUID tenantId, String alias); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java index d2192d0c17..53e247b67e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java @@ -41,7 +41,7 @@ public class SqlPartitioningRepository { @Autowired private JdbcTemplate jdbcTemplate; - private static final String SELECT_PARTITIONS_STMT = "SELECT tablename from pg_tables WHERE schemaname = 'public' and tablename like concat(?, '_%')"; + private static final String SELECT_PARTITIONS_STMT = "SELECT tablename from pg_tables WHERE schemaname = current_schema() and tablename like concat(?, '_%')"; private static final int PSQL_VERSION_14 = 140000; private volatile Integer currentServerVersion; diff --git a/dao/src/main/resources/sql/schema-ts-psql.sql b/dao/src/main/resources/sql/schema-ts-psql.sql index a697e341d9..917dfd876f 100644 --- a/dao/src/main/resources/sql/schema-ts-psql.sql +++ b/dao/src/main/resources/sql/schema-ts-psql.sql @@ -72,7 +72,7 @@ BEGIN IF partition_type = 'YEARS' THEN FOR partition IN SELECT tablename FROM pg_tables - WHERE schemaname = 'public' + WHERE schemaname = current_schema() AND tablename like 'ts_kv_' || '%' AND tablename != 'ts_kv_latest' AND tablename != 'key_dictionary' @@ -93,7 +93,7 @@ BEGIN ELSE FOR partition IN SELECT tablename FROM pg_tables - WHERE schemaname = 'public' + WHERE schemaname = current_schema() AND tablename like 'ts_kv_' || '%' AND tablename != 'ts_kv_latest' AND tablename != 'key_dictionary' @@ -135,7 +135,7 @@ BEGIN ELSE FOR partition IN SELECT tablename FROM pg_tables - WHERE schemaname = 'public' + WHERE schemaname = current_schema() AND tablename like 'ts_kv_' || '%' AND tablename != 'ts_kv_latest' AND tablename != 'key_dictionary' @@ -205,7 +205,7 @@ BEGIN IF partition IS NOT NULL THEN IF NOT EXISTS(SELECT FROM pg_tables - WHERE schemaname = 'public' + WHERE schemaname = current_schema() AND tablename = partition) THEN partition := NULL; RAISE NOTICE 'Failed to found partition by ttl'; diff --git a/ui-ngx/src/app/core/auth/auth.models.ts b/ui-ngx/src/app/core/auth/auth.models.ts index d94f26bd0f..623ceefadd 100644 --- a/ui-ngx/src/app/core/auth/auth.models.ts +++ b/ui-ngx/src/app/core/auth/auth.models.ts @@ -35,6 +35,7 @@ export interface SysParamsState { minAllowedAggregationIntervalInSecForCF: number; minAllowedScheduledUpdateIntervalInSecForCF: number; maxRelationLevelPerCfArgument: number; + maxRelatedEntitiesToReturnPerCfArgument: number; ruleChainDebugPerTenantLimitsConfiguration?: string; calculatedFieldDebugPerTenantLimitsConfiguration?: string; intermediateAggregationIntervalInSecForCF: number; diff --git a/ui-ngx/src/app/core/auth/auth.reducer.ts b/ui-ngx/src/app/core/auth/auth.reducer.ts index 41af25f732..51e02b1ab8 100644 --- a/ui-ngx/src/app/core/auth/auth.reducer.ts +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -37,6 +37,7 @@ const emptyUserAuthState: AuthPayload = { minAllowedAggregationIntervalInSecForCF: 0, minAllowedScheduledUpdateIntervalInSecForCF: 0, maxRelationLevelPerCfArgument: 0, + maxRelatedEntitiesToReturnPerCfArgument: 0, maxDataPointsPerRollingArg: 0, maxDebugModeDurationMinutes: 0, intermediateAggregationIntervalInSecForCF: 0, diff --git a/ui-ngx/src/app/core/services/calculated-field-form.service.ts b/ui-ngx/src/app/core/services/calculated-field-form.service.ts index 673b97abe4..1b9dddfd26 100644 --- a/ui-ngx/src/app/core/services/calculated-field-form.service.ts +++ b/ui-ngx/src/app/core/services/calculated-field-form.service.ts @@ -101,7 +101,12 @@ export class CalculatedFieldFormService { return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) .pipe( switchMap(event => { - const args = event?.arguments ? JSON.parse(event.arguments) : null; + let args = null; + if (event?.arguments) { + try { + args = JSON.parse(event.arguments); + } catch (e) {} + } return testDialogFn(formValue, args, false, expression); }), takeUntilDestroyed(destroyRef) @@ -109,4 +114,4 @@ export class CalculatedFieldFormService { } return testDialogFn(formValue, null, false, expression); } -} \ No newline at end of file +} diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html index 47f2e2e5be..7b1f13756c 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html @@ -79,6 +79,7 @@ [entityId]="entityId" [tenantId]="tenantId" [ownerId]="ownerId" + [isEditValue]="isEditValue" [watchKeyChange]="true" [entityName]="entityName"/> @@ -97,14 +98,16 @@ - + @if (isEditValue) { + + }
alarm-rule.no-clear-alarm-rule diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts index 6f28a01088..dbb5693d52 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts @@ -53,7 +53,7 @@ import { EntityService } from '@core/http/entity.service'; @Component({ selector: 'tb-alarm-rules', templateUrl: './alarm-rules.component.html', - styleUrls: [] + styleUrls: ['./alarm-rule-dialog.component.scss'] }) export class AlarmRulesComponent extends EntityComponent { @@ -175,7 +175,8 @@ export class AlarmRulesComponent extends EntityComponent
@@ -75,7 +76,7 @@ matTooltip="{{ 'calculated-fields.test-script-function' | translate }}" matTooltipPosition="above" class="tb-mat-32" - [disabled]="!argumentsList.length" + [disabled]="!argumentsList.length || readonly" (click)="onTestScript($event)"> bug_report @@ -84,7 +85,7 @@ @@ -111,7 +112,7 @@ required> } @else { - {{ 'alarm-rule.no-filter-preview' | translate }} + {{ 'alarm-rule.no-filter-preview' | translate }} } @@ -135,7 +136,7 @@ {{ 'alarm-rule.condition-type-hint' | translate }} } - @if (conditionFormGroup.get('type').value == AlarmConditionType.DURATION) { + @if (conditionFormGroup.get('type').value === AlarmConditionType.DURATION) {
{{ 'alarm-rule.value' | translate }}
@@ -167,7 +168,7 @@
- } @else if (conditionFormGroup.get('type').value == AlarmConditionType.REPEATING) { + } @else if (conditionFormGroup.get('type').value === AlarmConditionType.REPEATING) {
{{ 'alarm-rule.value' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts index b957428169..f39ed364fd 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts @@ -196,6 +196,10 @@ export class CfAlarmRuleConditionDialogComponent extends DialogComponent -
@@ -63,7 +63,7 @@
-
+
@@ -90,7 +90,7 @@
-
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.html index 8e265f5033..18ac406c97 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.html @@ -48,7 +48,7 @@
} @if (!createAlarmRulesFormArray().controls.length) { - + alarm-rule.add-create-alarm-rule-prompt } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.html index 194e1cb715..5ca3cde37b 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.html @@ -52,10 +52,12 @@ cdkFocusInitial> {{'action.cancel' | translate }} - + @if (!readonly) { + + } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.ts index 1d649882e6..017cefb890 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-complex-filter-predicate-dialog.component.ts @@ -36,6 +36,7 @@ export interface AlarmRuleComplexFilterPredicateDialogData { valueType: EntityKeyValueType; arguments: Record; argumentInUse: string; + readonly: boolean; } @Component({ @@ -63,6 +64,8 @@ export class AlarmRuleComplexFilterPredicateDialogComponent extends arguments = this.data.arguments; + readonly = this.data.readonly; + constructor(protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AlarmRuleComplexFilterPredicateDialogData, @@ -73,6 +76,10 @@ export class AlarmRuleComplexFilterPredicateDialogComponent extends this.isAdd = this.data.isAdd; this.complexFilterFormGroup.patchValue(this.data.complexPredicate, {emitEvent: false}); + + if (this.readonly) { + this.complexFilterFormGroup.disable({emitEvent: false}); + } } cancel(): void { diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.html index 182e64da89..8f6cd68c6a 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.html @@ -46,7 +46,7 @@ } - + filter.value-type.value-type @@ -93,10 +93,12 @@ cdkFocusInitial> {{ 'action.cancel' | translate }} - + @if (!readonly) { + + } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.ts index 7ca1e744d1..ba3adc1695 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-dialog.component.ts @@ -39,6 +39,7 @@ isAdd: boolean; arguments: Record; usedArguments: Array; + readonly: boolean; } @Component({ @@ -66,6 +67,8 @@ export class AlarmRuleFilterDialogComponent extends DialogComponent; + readonly = this.data.readonly; + constructor(protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AlarmRuleFilterDialogData, @@ -114,6 +117,10 @@ export class AlarmRuleFilterDialogComponent extends DialogComponent
-
{{ filterControl.value?.argument }}
-
{{ FilterPredicateTypeTranslationMap.get(filterControl.value?.valueType) | translate }}
+
{{ filterControl.value?.argument }}
+
{{ FilterPredicateTypeTranslationMap.get(filterControl.value?.valueType) | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts index 006c0129e4..77cc962747 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/filter/alarm-rule-filter-predicate.component.ts @@ -203,6 +203,7 @@ export class AlarmRuleFilterPredicateComponent implements ControlValueAccessor, isAdd: false, arguments: this.arguments, argumentInUse: this.argumentInUse, + readonly: this.disabled } }).afterClosed().subscribe( (result) => { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html index 3fa48395a9..39692168aa 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html @@ -101,6 +101,7 @@ [entityName]="entityName" [ownerId]="ownerId" [tenantId]="tenantId" + [isEditValue]="isEditValue" > } @case (CalculatedFieldType.PROPAGATION) { @@ -111,6 +112,7 @@ [tenantId]="tenantId" [ownerId]="ownerId" [testScript]="onTestScript.bind(this)" + [isEditValue]="isEditValue" > } @case (CalculatedFieldType.RELATED_ENTITIES_AGGREGATION) { @@ -120,6 +122,7 @@ [entityName]="entityName" [tenantId]="tenantId" [testScript]="onTestScript.bind(this)" + [isEditValue]="isEditValue" > } @case (CalculatedFieldType.ENTITY_AGGREGATION) { @@ -128,6 +131,7 @@ [entityId]="entityId" [entityName]="entityName" [tenantId]="tenantId" + [isEditValue]="isEditValue" > } @default { @@ -139,6 +143,7 @@ [tenantId]="tenantId" [isScript]="entityForm.get('type').value === CalculatedFieldType.SCRIPT" [testScript]="onTestScript.bind(this)" + [isEditValue]="isEditValue" > } } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html index d726dadcda..fc3c6ac7b3 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html @@ -93,7 +93,7 @@
- @if (disable) { + @if (!isEditValue) {
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 719e1a0103..11c95352d3 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 @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input } from '@angular/core'; +import { booleanAttribute, Component, forwardRef, Input } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -87,6 +87,8 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor @Input() testScript: (expression?: string) => Observable; + @Input({transform: booleanAttribute}) isEditValue = true; + readonly minAllowedAggregationIntervalInSecForCF = getCurrentAuthState(this.store).minAllowedAggregationIntervalInSecForCF; readonly intermediateAggregationIntervalInSecForCF = getCurrentAuthState(this.store).intermediateAggregationIntervalInSecForCF; readonly DayInSec = DAY / SECOND; diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html index 20d6cd3f1c..92fded8a99 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html @@ -96,7 +96,7 @@
- @if (disable) { + @if (!isEditValue) {