diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java index 63f398bbdc..0ec0a941d7 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java @@ -22,13 +22,13 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.EqualsAndHashCode; import net.objecthunter.exp4j.Expression; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.script.api.tbel.TbUtils; import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Output; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.service.cf.CalculatedFieldResult; import org.thingsboard.server.service.cf.TelemetryCalculatedFieldResult; +import org.thingsboard.common.util.NumberUtils; import java.util.Map; @@ -52,7 +52,7 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState { double expressionResult = ctx.evaluateSimpleExpression(expression.get(), this); Output output = ctx.getOutput(); - Object result = TbUtils.roundResult(expressionResult, output.getDecimalsByDefault()); + Object result = NumberUtils.roundResult(expressionResult, output.getDecimalsByDefault()); JsonNode outputResult = createResultJson(ctx.isUseLatestTs(), output.getName(), result); return Futures.immediateFuture(TelemetryCalculatedFieldResult.builder() diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/AvgAggEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/AvgAggEntry.java index ee06ae1529..5ee6548f29 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/AvgAggEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/AvgAggEntry.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.service.cf.ctx.state.aggregation.function; -import org.thingsboard.script.api.tbel.TbUtils; import org.thingsboard.server.common.data.cf.configuration.aggregation.AggFunction; +import org.thingsboard.common.util.NumberUtils; import java.math.BigDecimal; import java.math.RoundingMode; @@ -37,7 +37,7 @@ public class AvgAggEntry extends BaseAggEntry { @Override protected Object prepareResult(Integer precision) { double result = sum.divide(BigDecimal.valueOf(count), RoundingMode.HALF_UP).doubleValue(); - return TbUtils.roundResult(result, precision); + return NumberUtils.roundResult(result, precision); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/MaxAggEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/MaxAggEntry.java index b006d8fcd9..f1054bb326 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/MaxAggEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/MaxAggEntry.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.service.cf.ctx.state.aggregation.function; -import org.thingsboard.script.api.tbel.TbUtils; import org.thingsboard.server.common.data.cf.configuration.aggregation.AggFunction; +import org.thingsboard.common.util.NumberUtils; public class MaxAggEntry extends BaseAggEntry { @@ -31,7 +31,7 @@ public class MaxAggEntry extends BaseAggEntry { @Override protected Object prepareResult(Integer precision) { - return TbUtils.roundResult(max, precision); + return NumberUtils.roundResult(max, precision); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/MinAggEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/MinAggEntry.java index 2ce561d381..fc4257dfcb 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/MinAggEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/MinAggEntry.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.service.cf.ctx.state.aggregation.function; -import org.thingsboard.script.api.tbel.TbUtils; import org.thingsboard.server.common.data.cf.configuration.aggregation.AggFunction; +import org.thingsboard.common.util.NumberUtils; public class MinAggEntry extends BaseAggEntry { @@ -31,7 +31,7 @@ public class MinAggEntry extends BaseAggEntry { @Override protected Object prepareResult(Integer precision) { - return TbUtils.roundResult(min, precision); + return NumberUtils.roundResult(min, precision); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/SumAggEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/SumAggEntry.java index 3639e36249..9908757e56 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/SumAggEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/function/SumAggEntry.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.service.cf.ctx.state.aggregation.function; -import org.thingsboard.script.api.tbel.TbUtils; import org.thingsboard.server.common.data.cf.configuration.aggregation.AggFunction; +import org.thingsboard.common.util.NumberUtils; import java.math.BigDecimal; @@ -33,7 +33,7 @@ public class SumAggEntry extends BaseAggEntry { @Override protected Object prepareResult(Integer precision) { - return TbUtils.roundResult(sum.doubleValue(), precision); + return NumberUtils.roundResult(sum.doubleValue(), precision); } @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 1297a22478..c945868576 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 @@ -23,7 +23,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.common.util.DebugModeUtil; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.script.api.tbel.TbUtils; import org.thingsboard.script.api.tbel.TbelCfArg; import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.common.data.cf.CalculatedFieldType; @@ -41,6 +40,7 @@ import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.BaseCalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx; import org.thingsboard.server.service.cf.ctx.state.SingleValueArgumentEntry; +import org.thingsboard.common.util.NumberUtils; import java.time.Instant; import java.time.ZoneId; @@ -291,7 +291,7 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt ArgumentEntry argumentEntry = entry.getValue(); if (!argumentEntry.isEmpty()) { Object resultValue = argumentEntry.getValue() instanceof Number number - ? TbUtils.roundResult(number.doubleValue(), precision) + ? NumberUtils.roundResult(number.doubleValue(), precision) : argumentEntry.getValue(); metricsNode.put(metricName, JacksonUtil.toString(resultValue)); } diff --git a/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java index 7530c646f4..a358c0f12c 100644 --- a/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.queue.TbCallback; @@ -40,6 +41,7 @@ import java.lang.reflect.Field; import java.time.LocalDate; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -47,7 +49,10 @@ import static java.time.ZoneOffset.UTC; import static java.time.temporal.ChronoField.DAY_OF_MONTH; import static java.time.temporal.ChronoUnit.MONTHS; import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; +import static org.thingsboard.server.common.data.ApiFeature.SMS; +import static org.thingsboard.server.common.data.ApiUsageRecordKey.SMS_EXEC_COUNT; @DaoSqlTest public class DefaultTbApiUsageStateServiceTest extends AbstractControllerTest { @@ -157,6 +162,85 @@ public class DefaultTbApiUsageStateServiceTest extends AbstractControllerTest { assertThat(tenantApiUsageState.getNextCycleTs()).isEqualTo(firstOfNextMonth); } + @Test + public void checkTenantCreatedWithSmsDisabledApiUsage() throws Exception { + TenantProfile tenantProfile = new TenantProfile(); + tenantProfile.setName("Test profile"); + TenantProfileData tenantProfileData = new TenantProfileData(); + DefaultTenantProfileConfiguration config = DefaultTenantProfileConfiguration.builder() + .smsEnabled(false) + .build(); + + tenantProfileData.setConfiguration(config); + tenantProfile.setProfileData(tenantProfileData); + tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); + + Tenant tenant = new Tenant(); + tenant.setTitle("My test tenant"); + tenant.setTenantProfileId(tenantProfile.getId()); + tenant = saveTenant(tenant); + + ApiUsageState apiUsageState = apiUsageStateService.findApiUsageStateByEntityId(tenant.getId()); + assertThat(apiUsageState.getSmsExecState()).isEqualTo(ApiUsageStateValue.DISABLED); + + TenantId finalTenantId = tenant.getId(); + ApiUsageStateId finalApiUsageStateId = apiUsageState.getId(); + + + await().atMost(5, TimeUnit.SECONDS).until(() -> { + Optional smsApiState = tsService.findLatest(finalTenantId, finalApiUsageStateId, SMS.getApiStateKey()).get(); + return smsApiState.isPresent() && smsApiState.get().getValueAsString().equals(ApiUsageStateValue.DISABLED.name()); + }); + + await().atMost(5, TimeUnit.SECONDS).until(() -> { + Optional smsApiState = tsService.findLatest(finalTenantId, finalApiUsageStateId, SMS_EXEC_COUNT.getApiLimitKey()).get(); + return smsApiState.isPresent() && smsApiState.get().getLongValue().get().equals(0L); + }); + + // enable SMS and check that the ApiUsageState is updated accordingly + config = DefaultTenantProfileConfiguration.builder() + .smsEnabled(true) + .maxSms(10) + .build(); + tenantProfileData.setConfiguration(config); + tenantProfile.setProfileData(tenantProfileData); + doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); + + await().atMost(5, TimeUnit.SECONDS).until(() -> + apiUsageStateService.findApiUsageStateByEntityId(finalTenantId).getSmsExecState() == ApiUsageStateValue.ENABLED); + + await().atMost(5, TimeUnit.SECONDS).until(() -> { + Optional smsApiState = tsService.findLatest(finalTenantId, finalApiUsageStateId, SMS.getApiStateKey()).get(); + return smsApiState.isPresent() && smsApiState.get().getValueAsString().equals(ApiUsageStateValue.ENABLED.name()); + }); + + await().atMost(5, TimeUnit.SECONDS).until(() -> { + Optional smsApiState = tsService.findLatest(finalTenantId, finalApiUsageStateId, SMS_EXEC_COUNT.getApiLimitKey()).get(); + return smsApiState.isPresent() && smsApiState.get().getLongValue().get().equals(10L); + }); + + //disable SMS and check that the ApiUsageState is updated accordingly + config = DefaultTenantProfileConfiguration.builder() + .smsEnabled(false) + .build(); + tenantProfileData.setConfiguration(config); + tenantProfile.setProfileData(tenantProfileData); + doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); + + await().atMost(5, TimeUnit.SECONDS).until(() -> + apiUsageStateService.findApiUsageStateByEntityId(finalTenantId).getSmsExecState() == ApiUsageStateValue.DISABLED); + + await().atMost(5, TimeUnit.SECONDS).until(() -> { + Optional smsApiState = tsService.findLatest(finalTenantId, finalApiUsageStateId, SMS.getApiStateKey()).get(); + return smsApiState.isPresent() && smsApiState.get().getValueAsString().equals(ApiUsageStateValue.DISABLED.name()); + }); + + await().atMost(5, TimeUnit.SECONDS).until(() -> { + Optional smsApiState = tsService.findLatest(finalTenantId, finalApiUsageStateId, SMS_EXEC_COUNT.getApiLimitKey()).get(); + return smsApiState.isPresent() && smsApiState.get().getLongValue().get().equals(0L); + }); + } + private TenantProfile createTenantProfile() { TenantProfile tenantProfile = new TenantProfile(); tenantProfile.setName("Tenant Profile"); diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java index c0bdf122c3..7fb56a89ff 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java @@ -26,6 +26,7 @@ import org.mvel2.execution.ExecutionHashMap; import org.mvel2.execution.ExecutionLinkedHashSet; import org.mvel2.util.MethodStub; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.NumberUtils; import org.thingsboard.common.util.geo.Coordinates; import org.thingsboard.common.util.geo.GeoUtil; import org.thingsboard.common.util.geo.RangeUnit; @@ -35,7 +36,6 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.BigInteger; -import java.math.RoundingMode; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.ByteBuffer; @@ -258,15 +258,13 @@ public class TbUtils { byte[].class, int.class, int.class))); parserConfig.addImport("parseBytesLongToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesLongToDouble", byte[].class, int.class, int.class, boolean.class))); - parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed", + parserConfig.addImport("toFixed", new MethodStub(NumberUtils.class.getMethod("toFixed", double.class, int.class))); - parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed", + parserConfig.addImport("toFixed", new MethodStub(NumberUtils.class.getMethod("toFixed", float.class, int.class))); - parserConfig.addImport("toInt", new MethodStub(TbUtils.class.getMethod("toInt", + parserConfig.addImport("toInt", new MethodStub(NumberUtils.class.getMethod("toInt", double.class))); - parserConfig.addImport("roundResult", new MethodStub(TbUtils.class.getMethod("roundResult", - double.class, Integer.class))); - parserConfig.addImport("isNaN", new MethodStub(TbUtils.class.getMethod("isNaN", + parserConfig.addImport("isNaN", new MethodStub(NumberUtils.class.getMethod("isNaN", double.class))); parserConfig.addImport("hexToBytes", new MethodStub(TbUtils.class.getMethod("hexToBytes", ExecutionContext.class, String.class))); @@ -1176,32 +1174,6 @@ public class TbUtils { return new String(hexChars, StandardCharsets.UTF_8); } - public static double toFixed(double value, int precision) { - return BigDecimal.valueOf(value).setScale(precision, RoundingMode.HALF_UP).doubleValue(); - } - - public static float toFixed(float value, int precision) { - return BigDecimal.valueOf(value).setScale(precision, RoundingMode.HALF_UP).floatValue(); - } - - public static int toInt(double value) { - return BigDecimal.valueOf(value).setScale(0, RoundingMode.HALF_UP).intValue(); - } - - public static Object roundResult(double value, Integer precision) { - if (precision == null) { - return value; - } - if (precision.equals(0)) { - return toInt(value); - } - return toFixed(value, precision); - } - - public static boolean isNaN(double value) { - return Double.isNaN(value); - } - public static ExecutionHashMap toFlatMap(ExecutionContext ctx, Map json) { return toFlatMap(ctx, json, new ArrayList<>(), true); } @@ -1220,7 +1192,6 @@ public class TbUtils { return map; } - public static String encodeURI(String uri) { String encoded = URLEncoder.encode(uri, StandardCharsets.UTF_8); for (var entry : mdnEncodingReplacements.entrySet()) { diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java index 9feeb414dc..bdafeac8a7 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java @@ -279,13 +279,6 @@ public class TbUtilsTest { Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseLittleEndianHexToFloat(floatValHexRev))); } - @Test - public void toFixedFloat() { - float actualF = TbUtils.toFixed(floatVal, 3); - Assertions.assertEquals(1, Float.compare(floatVal, actualF)); - Assertions.assertEquals(0, Float.compare(29.298f, actualF)); - } - @Test public void parseBytesToFloat() { byte[] floatValByte = {0x0A}; @@ -461,13 +454,6 @@ public class TbUtilsTest { Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseLittleEndianHexToDouble(longValHexRev))); } - @Test - public void toFixedDouble() { - double actualD = TbUtils.toFixed(doubleVal, 3); - Assertions.assertEquals(-1, Double.compare(doubleVal, actualD)); - Assertions.assertEquals(0, Double.compare(1729.173, actualD)); - } - @Test public void parseBytesToDouble() { byte[] doubleValByte = {0x0A}; @@ -1147,26 +1133,6 @@ public class TbUtilsTest { Assertions.assertEquals(expected, actual); } - @Test - public void toInt() { - Assertions.assertEquals(1729, TbUtils.toInt(doubleVal)); - Assertions.assertEquals(13, TbUtils.toInt(12.8)); - Assertions.assertEquals(28, TbUtils.toInt(28.0)); - } - - @Test - public void roundResult() { - Assertions.assertEquals(1729.1729, TbUtils.roundResult(doubleVal, null)); - Assertions.assertEquals(1729, TbUtils.roundResult(doubleVal, 0)); - Assertions.assertEquals(1729.17, TbUtils.roundResult(doubleVal, 2)); - } - - @Test - public void isNaN() { - assertFalse(TbUtils.isNaN(doubleVal)); - assertTrue(TbUtils.isNaN(Double.NaN)); - } - @Test public void isInsidePolygon() { // outside the polygon diff --git a/common/util/src/main/java/org/thingsboard/common/util/NumberUtils.java b/common/util/src/main/java/org/thingsboard/common/util/NumberUtils.java new file mode 100644 index 0000000000..6681557c87 --- /dev/null +++ b/common/util/src/main/java/org/thingsboard/common/util/NumberUtils.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2026 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.common.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +public class NumberUtils { + + public static boolean isNaN(double value) { + return Double.isNaN(value); + } + + public static double toFixed(double value, int precision) { + return BigDecimal.valueOf(value).setScale(precision, RoundingMode.HALF_UP).doubleValue(); + } + + public static float toFixed(float value, int precision) { + return BigDecimal.valueOf(value).setScale(precision, RoundingMode.HALF_UP).floatValue(); + } + + public static int toInt(double value) { + return BigDecimal.valueOf(value).setScale(0, RoundingMode.HALF_UP).intValue(); + } + + public static Object roundResult(double value, Integer precision) { + if (precision == null) { + return value; + } + if (precision.equals(0)) { + return toInt(value); + } + return toFixed(value, precision); + } + +} diff --git a/common/util/src/test/java/org/thingsboard/common/util/NumberUtilsTest.java b/common/util/src/test/java/org/thingsboard/common/util/NumberUtilsTest.java new file mode 100644 index 0000000000..1a1bf326e6 --- /dev/null +++ b/common/util/src/test/java/org/thingsboard/common/util/NumberUtilsTest.java @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2026 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.common.util; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class NumberUtilsTest { + + private final Float floatVal = 29.29824f; + private final double doubleVal = 1729.1729; + + @Test + public void isNaN() { + assertThat(NumberUtils.isNaN(doubleVal)).isFalse(); + assertThat(NumberUtils.isNaN(Double.NaN)).isTrue(); + } + + @Test + public void toFixedFloat() { + float actualF = NumberUtils.toFixed(floatVal, 3); + assertThat(Float.compare(floatVal, actualF)).isEqualTo(1); + assertThat(Float.compare(29.298f, actualF)).isEqualTo(0); + } + + @Test + public void toFixedDouble() { + double actualD = NumberUtils.toFixed(doubleVal, 3); + assertThat(Double.compare(doubleVal, actualD)).isEqualTo(-1); + assertThat(Double.compare(1729.173, actualD)).isEqualTo(0); + } + + @Test + public void toInt() { + assertThat(NumberUtils.toInt(doubleVal)).isEqualTo(1729); + assertThat(NumberUtils.toInt(12.8)).isEqualTo(13); + assertThat(NumberUtils.toInt(28.0)).isEqualTo(28); + } + + @Test + public void roundResult() { + assertThat(NumberUtils.roundResult(doubleVal, null)).isEqualTo(1729.1729); + assertThat(NumberUtils.roundResult(doubleVal, 0)).isEqualTo(1729); + assertThat(NumberUtils.roundResult(doubleVal, 2)).isEqualTo(1729.17); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index b97c81c612..7f0e507f5f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; @@ -101,6 +101,21 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A entityId = Objects.requireNonNullElse(entityId, tenantId); log.trace("Executing createDefaultUsageRecord [{}]", entityId); validateId(tenantId, id -> INCORRECT_TENANT_ID + id); + + ApiUsageStateValue smsApiUsageState = ApiUsageStateValue.ENABLED; + + DefaultTenantProfileConfiguration configuration = null; + if (entityId.getEntityType() == EntityType.TENANT && !entityId.equals(TenantId.SYS_TENANT_ID)) { + tenantId = (TenantId) entityId; + Tenant tenant = tenantService.findTenantById(tenantId); + TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); + configuration = (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration(); + + if (configuration.getSmsEnabled() != null && !configuration.getSmsEnabled()) { + smsApiUsageState = ApiUsageStateValue.DISABLED; + } + } + ApiUsageState apiUsageState = new ApiUsageState(); apiUsageState.setTenantId(tenantId); apiUsageState.setEntityId(entityId); @@ -109,7 +124,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A apiUsageState.setJsExecState(ApiUsageStateValue.ENABLED); apiUsageState.setTbelExecState(ApiUsageStateValue.ENABLED); apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED); - apiUsageState.setSmsExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setSmsExecState(smsApiUsageState); apiUsageState.setEmailExecState(ApiUsageStateValue.ENABLED); apiUsageState.setAlarmExecState(ApiUsageStateValue.ENABLED); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); @@ -138,17 +153,12 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.EMAIL.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), - new StringDataEntry(ApiFeature.SMS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + new StringDataEntry(ApiFeature.SMS.getApiStateKey(), smsApiUsageState.name()))); apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.ALARM.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); - if (entityId.getEntityType() == EntityType.TENANT && !entityId.equals(TenantId.SYS_TENANT_ID)) { - tenantId = (TenantId) entityId; - Tenant tenant = tenantService.findTenantById(tenantId); - TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); - TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); - + if (configuration != null) { List profileThresholds = new ArrayList<>(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { if (key.getApiLimitKey() == null) continue; 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 e82a61132f..be674b78fc 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 @@ -17,7 +17,7 @@ import { Component, forwardRef, Input } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { DefaultTenantProfileConfiguration, FormControlsFrom } from '@shared/models/tenant.model'; -import { isDefinedAndNotNull } from '@core/utils'; +import { isDefinedAndNotNull, isUndefinedOrNull} from '@core/utils'; import { RateLimitsType } from './rate-limits/rate-limits.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { coerceBoolean } from '@shared/decorators/coercion'; @@ -170,6 +170,9 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA writeValue(value: DefaultTenantProfileConfiguration | null): void { if (isDefinedAndNotNull(value)) { + if (isUndefinedOrNull(value.smsEnabled)) { + value.smsEnabled = true; + } this.maxSmsValidation(value.smsEnabled); this.tenantProfileConfigurationForm.patchValue(value, {emitEvent: false}); } diff --git a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts index 9e6d153786..0593cae081 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts @@ -83,6 +83,11 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI const providers = setting.providers.filter(provider => provider.enable); providers.forEach(provider => delete provider.enable); const enforcedUsersFilter = this.twoFaFormGroup.get('enforcedUsersFilter').value; + if (enforcedUsersFilter.filterByTenants) { + enforcedUsersFilter.tenantProfilesIds = null; + } else { + enforcedUsersFilter.tenantsIds = null; + } delete enforcedUsersFilter.filterByTenants; const config = Object.assign(setting, {providers}, {enforcedUsersFilter}); this.filterByTenants = this.twoFaFormGroup.get('enforcedUsersFilter.filterByTenants').value; @@ -142,7 +147,7 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI verificationCodeCheckRateLimitEnable: [false], verificationCodeCheckRateLimitNumber: [{value: 3, disabled: true}, this.posIntValidation], verificationCodeCheckRateLimitTime: [{value: 900, disabled: true}, this.posIntValidation], - minVerificationCodeSendPeriod: ['30', [Validators.required, Validators.min(5), Validators.pattern(/^\d*$/)]], + minVerificationCodeSendPeriod: [30, [Validators.required, Validators.min(5), Validators.pattern(/^\d*$/)]], providers: this.fb.array([]) }); Object.values(TwoFactorAuthProviderType).forEach(provider => { diff --git a/ui-ngx/src/assets/dashboard/api_usage.json b/ui-ngx/src/assets/dashboard/api_usage.json index 226c570753..b5e26f6ec8 100644 --- a/ui-ngx/src/assets/dashboard/api_usage.json +++ b/ui-ngx/src/assets/dashboard/api_usage.json @@ -11412,8 +11412,8 @@ "label": "{i18n:api-usage.sms}", "state": "sms", "status": { - "name": "notificationApiState", - "label": "notificationApiState", + "name": "smsApiState", + "label": "smsApiState", "type": "timeseries", "settings": {}, "color": "#2196f3"