From 97cc1ab0da9f6f108887a94a3a66c46108fbbc87 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 20 Apr 2023 18:23:38 +0300 Subject: [PATCH] Improve entities count limit notification rule --- .../server/actors/ActorSystemContext.java | 2 +- .../server/controller/BaseController.java | 6 +- .../service/action/EntityActionService.java | 11 +- .../DefaultTbApiUsageStateService.java | 2 +- .../DefaultNotificationRuleProcessor.java | 2 +- .../EntitiesLimitTriggerProcessor.java | 15 +- .../queue/DefaultTbCoreConsumerService.java | 2 +- .../DefaultAlarmSubscriptionService.java | 2 +- .../service/update/DefaultUpdateService.java | 3 +- .../MockNotificationSettingsService.java | 7 +- .../notification/NotificationRuleApiTest.java | 177 +++++++++++++----- .../trigger/EntitiesLimitTrigger.java | 5 +- .../NotificationRuleProcessor.java | 2 +- .../RemoteNotificationRuleProcessor.java | 1 - .../usagerecord/DefaultApiLimitService.java | 13 -- 15 files changed, 170 insertions(+), 80 deletions(-) rename common/{message/src/main/java/org/thingsboard/server/common/msg => queue/src/main/java/org/thingsboard/server/queue}/notification/NotificationRuleProcessor.java (93%) diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 26eab30d5d..e9d89ad6a2 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -89,7 +89,7 @@ import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; -import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; +import org.thingsboard.server.queue.notification.NotificationRuleProcessor; import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.component.ComponentDiscoveryService; diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 8a892b91a9..5104fe9d32 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -797,8 +797,10 @@ public abstract class BaseController { protected > void logEntityAction(SecurityUser user, EntityType entityType, E entity, E savedEntity, ActionType actionType, Exception e) { EntityId entityId = savedEntity != null ? savedEntity.getId() : emptyId(entityType); - entityActionService.logEntityAction(user, entityId, savedEntity != null ? savedEntity : entity, - user.getCustomerId(), actionType, e); + if (!user.isSystemAdmin()) { + entityActionService.logEntityAction(user, entityId, savedEntity != null ? savedEntity : entity, + user.getCustomerId(), actionType, e); + } } protected > E doSaveAndLog(EntityType entityType, E entity, BiFunction savingFunction) throws Exception { diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java index d39e200337..68ba205afd 100644 --- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java +++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java @@ -41,7 +41,9 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.notification.trigger.EntitiesLimitTrigger; import org.thingsboard.server.dao.audit.AuditLogService; +import org.thingsboard.server.queue.notification.NotificationRuleProcessor; import java.util.List; import java.util.Map; @@ -53,6 +55,7 @@ import java.util.stream.Collectors; public class EntityActionService { private final TbClusterService tbClusterService; private final AuditLogService auditLogService; + private final NotificationRuleProcessor notificationRuleProcessor; private static final ObjectMapper json = new ObjectMapper(); @@ -187,6 +190,12 @@ public class EntityActionService { AlarmComment comment = extractParameter(AlarmComment.class, 0, additionalInfo); metaData.putValue("comment", json.writeValueAsString(comment)); } + if (actionType == ActionType.ADDED) { + notificationRuleProcessor.process(EntitiesLimitTrigger.builder() + .tenantId(tenantId) + .entityType(entityId.getEntityType()) + .build()); + } ObjectNode entityNode; if (entity != null) { entityNode = json.valueToTree(entity); @@ -247,7 +256,7 @@ public class EntityActionService { } public void logEntityAction(User user, I entityId, E entity, CustomerId customerId, - ActionType actionType, Exception e, Object... additionalInfo) { + ActionType actionType, Exception e, Object... additionalInfo) { if (customerId == null || customerId.isNullUid()) { customerId = user.getCustomerId(); } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 0b3a4b8acd..9fdb1d0b8e 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -48,7 +48,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; -import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; +import org.thingsboard.server.queue.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.notification.trigger.ApiUsageLimitTrigger; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java index 8b3edbb812..57dba39063 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; -import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; +import org.thingsboard.server.queue.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger; import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java index da2ec7042e..95ace87653 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java @@ -21,7 +21,10 @@ import org.thingsboard.server.common.data.notification.info.EntitiesLimitNotific import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.msg.notification.trigger.EntitiesLimitTrigger; +import org.thingsboard.server.dao.entity.EntityCountService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; import static org.apache.commons.collections.CollectionUtils.isNotEmpty; @@ -30,6 +33,8 @@ import static org.apache.commons.collections.CollectionUtils.isNotEmpty; @RequiredArgsConstructor public class EntitiesLimitTriggerProcessor implements NotificationRuleTriggerProcessor { + private final EntityCountService entityCountService; + private final TbTenantProfileCache tenantProfileCache; private final TenantService tenantService; @Override @@ -37,7 +42,15 @@ public class EntitiesLimitTriggerProcessor implements NotificationRuleTriggerPro if (isNotEmpty(triggerConfig.getEntityTypes()) && !triggerConfig.getEntityTypes().contains(trigger.getEntityType())) { return false; } - return (int) (trigger.getLimit() * triggerConfig.getThreshold()) == trigger.getCurrentCount(); // strict comparing not to send notification on each new entity + DefaultTenantProfileConfiguration profileConfiguration = tenantProfileCache.get(trigger.getTenantId()).getDefaultProfileConfiguration(); + long limit = profileConfiguration.getEntitiesLimit(trigger.getEntityType()); + if (limit <= 0) { + return false; + } + long currentCount = entityCountService.countByTenantIdAndEntityType(trigger.getTenantId(), trigger.getEntityType()); + trigger.setLimit(limit); + trigger.setCurrentCount(currentCount); + return (int) (limit * triggerConfig.getThreshold()) == currentCount; // strict comparing not to send notification on each new entity } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index dafbfc60d2..6a6612c581 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; +import org.thingsboard.server.queue.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 07243711f3..2bb93d3dad 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -51,7 +51,7 @@ import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmService; -import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; +import org.thingsboard.server.queue.notification.NotificationRuleProcessor; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; diff --git a/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java index 0f80f0e3f6..612c1d6610 100644 --- a/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java @@ -29,11 +29,10 @@ import org.springframework.web.client.RestTemplate; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger; -import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; +import org.thingsboard.server.queue.notification.NotificationRuleProcessor; import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbCoreComponent; -import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.IOException; import java.nio.file.Files; diff --git a/application/src/test/java/org/thingsboard/server/service/notification/MockNotificationSettingsService.java b/application/src/test/java/org/thingsboard/server/service/notification/MockNotificationSettingsService.java index 84d8a458d5..a1cc82fd5a 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/MockNotificationSettingsService.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/MockNotificationSettingsService.java @@ -19,17 +19,14 @@ import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.notification.DefaultNotificationSettingsService; -import org.thingsboard.server.dao.notification.NotificationRuleService; -import org.thingsboard.server.dao.notification.NotificationTargetService; -import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.settings.AdminSettingsService; @Service @Primary public class MockNotificationSettingsService extends DefaultNotificationSettingsService { - public MockNotificationSettingsService(AdminSettingsService adminSettingsService, NotificationTargetService notificationTargetService, NotificationTemplateService notificationTemplateService, NotificationRuleService notificationRuleService) { - super(adminSettingsService, notificationTargetService, notificationTemplateService, notificationRuleService); + public MockNotificationSettingsService(AdminSettingsService adminSettingsService) { + super(adminSettingsService, null, null); } @Override diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index 8fec807d45..3c3ba6ccd5 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.BooleanNode; import org.junit.Before; import org.junit.Test; +import org.junit.function.ThrowingRunnable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.data.util.Pair; @@ -34,6 +35,7 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.device.profile.AlarmCondition; import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter; import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; @@ -41,6 +43,7 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; +import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; @@ -54,7 +57,9 @@ import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction; +import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; @@ -66,21 +71,27 @@ import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.service.apiusage.limits.LimitedApi; import org.thingsboard.server.service.apiusage.limits.RateLimitService; +import org.thingsboard.server.service.entitiy.tenant.profile.TbTenantProfileService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.UUID; +import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.offset; @@ -98,6 +109,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { @Autowired private TenantProfileService tenantProfileService; @Autowired + private TbTenantProfileService tbTenantProfileService; + @Autowired private RateLimitService rateLimitService; @Before @@ -107,56 +120,33 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { @Test public void testNotificationRuleProcessing_entityActionTrigger() throws Exception { - String notificationSubject = "${actionType}: ${entityType} [${entityId}]"; - String notificationText = "User: ${userEmail}"; - NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.GENERAL, notificationSubject, notificationText, NotificationDeliveryMethod.WEB); - - NotificationRule notificationRule = new NotificationRule(); - notificationRule.setName("Web notification when any device is created, updated or deleted"); - notificationRule.setTemplateId(notificationTemplate.getId()); - notificationRule.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION); - EntityActionNotificationRuleTriggerConfig triggerConfig = new EntityActionNotificationRuleTriggerConfig(); triggerConfig.setEntityTypes(Set.of(EntityType.DEVICE)); triggerConfig.setCreated(true); triggerConfig.setUpdated(true); triggerConfig.setDeleted(true); + createNotificationRule(triggerConfig, "${actionType}: ${entityType} [${entityId}]", + "User: ${userEmail}", createNotificationTarget(tenantAdminUserId).getId()); + + Device device = checkNotificationAfter(() -> { + return createDevice("DEVICE!!!", "default", "12345"); + }, (notification, newDevice) -> { + assertThat(notification.getSubject()).isEqualTo("added: Device [" + newDevice.getId() + "]"); + assertThat(notification.getText()).isEqualTo("User: " + TENANT_ADMIN_EMAIL); + }); - DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig(); - recipientsConfig.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION); - recipientsConfig.setTargets(List.of(createNotificationTarget(tenantAdminUserId).getUuidId())); - - notificationRule.setTriggerConfig(triggerConfig); - notificationRule.setRecipientsConfig(recipientsConfig); - notificationRule = saveNotificationRule(notificationRule); - - getWsClient().subscribeForUnreadNotifications(10).waitForReply(true); - - - getWsClient().registerWaitForUpdate(); - Device device = createDevice("DEVICE!!!", "default", "12345"); - getWsClient().waitForUpdate(true); - - Notification notification = getWsClient().getLastDataUpdate().getUpdate(); - assertThat(notification.getSubject()).isEqualTo("added: Device [" + device.getId() + "]"); - assertThat(notification.getText()).isEqualTo("User: " + TENANT_ADMIN_EMAIL); - - - getWsClient().registerWaitForUpdate(); - device.setName("Updated name"); - device = doPost("/api/device", device, Device.class); - getWsClient().waitForUpdate(true); - - notification = getWsClient().getLastDataUpdate().getUpdate(); - assertThat(notification.getSubject()).isEqualTo("updated: Device [" + device.getId() + "]"); - - - getWsClient().registerWaitForUpdate(); - doDelete("/api/device/" + device.getId()).andExpect(status().isOk()); - getWsClient().waitForUpdate(true); + checkNotificationAfter(() -> { + device.setName("Updated name"); + doPost("/api/device", device, Device.class); + }, notification -> { + assertThat(notification.getSubject()).isEqualTo("updated: Device [" + device.getId() + "]"); + }); - notification = getWsClient().getLastDataUpdate().getUpdate(); - assertThat(notification.getSubject()).isEqualTo("deleted: Device [" + device.getId() + "]"); + checkNotificationAfter(() -> { + doDelete("/api/device/" + device.getId()).andExpect(status().isOk()); + }, notification -> { + assertThat(notification.getSubject()).isEqualTo("deleted: Device [" + device.getId() + "]"); + }); } @Test @@ -215,7 +205,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { clients.forEach((expectedDelay, wsClient) -> { Notification notification = wsClient.getLastDataUpdate().getUpdate(); double actualDelay = (double) (notification.getCreatedTime() - ts) / 1000; - assertThat(actualDelay).isCloseTo(expectedDelay, offset(0.5)); + assertThat(actualDelay).isCloseTo(expectedDelay, offset(2.0)); assertThat(notification.getSubject()).isEqualTo("Alarm type: " + alarmType + ", status: " + AlarmStatus.ACTIVE_UNACK + ", " + "severity: " + AlarmSeverity.CRITICAL.toString().toLowerCase() + ", deviceId: " + device.getId()); @@ -312,6 +302,62 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { assertThat(findNotificationRequests(EntityType.ALARM).getData()).filteredOn(NotificationRequest::isScheduled).isEmpty(); } + @Test + public void testNotificationRuleProcessing_entitiesLimit() throws Exception { + TenantProfile tenantProfile = tenantProfileService.findDefaultTenantProfile(TenantId.SYS_TENANT_ID); + TenantProfileData profileData = tenantProfile.getProfileData(); + DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration) profileData.getConfiguration(); + int limit = 5; + profileConfiguration.setMaxDevices(limit); + profileConfiguration.setMaxAssets(limit); + profileConfiguration.setMaxCustomers(limit); + profileConfiguration.setMaxUsers(limit); + profileConfiguration.setMaxDashboards(limit); + profileConfiguration.setMaxRuleChains(limit); + tenantProfile.setProfileData(profileData); + tbTenantProfileService.save(TenantId.SYS_TENANT_ID, tenantProfile, null); + + EntitiesLimitNotificationRuleTriggerConfig triggerConfig = EntitiesLimitNotificationRuleTriggerConfig.builder() + .entityTypes(null).threshold(0.8f) + .build(); + loginSysAdmin(); + NotificationRule rule = createNotificationRule(triggerConfig, "${entityType}s limit will be reached soon", + "${entityType}s usage: ${currentCount}/${limit} (${percents}%)", createNotificationTarget(tenantAdminUserId).getId()); + int threshold = (int) (limit * 0.8); + loginTenantAdmin(); + + checkNotificationAfter(() -> { + for (int i = 1; i <= threshold; i++) { + createDevice(i + "", i + ""); + } + }, notification -> { + assertThat(notification.getText()).isEqualTo("Devices usage: " + threshold + "/" + limit + " (80%)"); + }); + + checkNotificationAfter(() -> { + for (int i = 1; i <= threshold; i++) { + Asset asset = new Asset(); + asset.setType("Test"); + asset.setName(i + ""); + doPost("/api/asset", asset); + } + }, notification -> { + assertThat(notification.getText()).isEqualTo("Assets usage: " + threshold + "/" + limit + " (80%)"); + }); + + triggerConfig.setThreshold(1.0f); + rule.setTriggerConfig(triggerConfig); + loginSysAdmin(); + saveNotificationRule(rule); + loginTenantAdmin(); + + checkNotificationAfter(() -> { + createDevice(limit + "", limit + ""); + }, notification -> { + assertThat(notification.getText()).isEqualTo("Devices usage: " + limit + "/" + limit + " (100%)"); + }); + } + @Test public void testNotificationRuleInfo() throws Exception { NotificationDeliveryMethod[] deliveryMethods = {NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.EMAIL}; @@ -345,9 +391,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration) profileData.getConfiguration(); profileConfiguration.setTenantNotificationRequestsPerRuleRateLimit(notificationRequestsLimit + ":300"); tenantProfile.setProfileData(profileData); - loginSysAdmin(); - doPost("/api/tenantProfile", tenantProfile).andExpect(status().isOk()); - loginTenantAdmin(); + tbTenantProfileService.save(TenantId.SYS_TENANT_ID, tenantProfile, null); NotificationRule rule = new NotificationRule(); rule.setName("Device created"); @@ -386,6 +430,45 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { assertThat(getMyNotifications(false, 100)).size().isEqualTo(notificationRequestsLimit); } + private R checkNotificationAfter(Callable action, BiConsumer check) throws Exception { + if (getWsClient().getLastDataUpdate() == null) { + getWsClient().subscribeForUnreadNotifications(10).waitForReply(true); + } + getWsClient().registerWaitForUpdate(); + R result = action.call(); + getWsClient().waitForUpdate(true); + check.accept(getWsClient().getLastDataUpdate().getUpdate(), result); + return result; + } + + private void checkNotificationAfter(ThrowingRunnable action, Consumer check) throws Exception { + checkNotificationAfter(() -> { + try { + action.run(); + return null; + } catch (Throwable e) { + throw new Exception(e); + } + }, (notification, r) -> check.accept(notification)); + } + + private NotificationRule createNotificationRule(NotificationRuleTriggerConfig triggerConfig, String subject, String text, NotificationTargetId... targets) { + NotificationTemplate template = createNotificationTemplate(NotificationType.valueOf(triggerConfig.getTriggerType().toString()), subject, text, NotificationDeliveryMethod.WEB); + + NotificationRule rule = new NotificationRule(); + rule.setName(triggerConfig.getTriggerType() + " [" + Arrays.toString(targets) + "]"); + rule.setTemplateId(template.getId()); + rule.setTriggerType(triggerConfig.getTriggerType()); + rule.setTriggerConfig(triggerConfig); + + DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig(); + recipientsConfig.setTriggerType(triggerConfig.getTriggerType()); + recipientsConfig.setTargets(DaoUtil.toUUIDs(List.of(targets))); + rule.setRecipientsConfig(recipientsConfig); + + return saveNotificationRule(rule); + } + private DeviceProfile createDeviceProfileWithAlarmRules(String alarmType) { DeviceProfile deviceProfile = createDeviceProfile("For notification rule test"); deviceProfile.setTenantId(tenantId); diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/EntitiesLimitTrigger.java b/common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/EntitiesLimitTrigger.java index ce6505fe13..e6ce143fd0 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/EntitiesLimitTrigger.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/EntitiesLimitTrigger.java @@ -28,8 +28,9 @@ public class EntitiesLimitTrigger implements NotificationRuleTrigger { private final TenantId tenantId; private final EntityType entityType; - private final long currentCount; - private final long limit; + + private long limit; + private long currentCount; @Override public NotificationRuleTriggerType getType() { diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/notification/NotificationRuleProcessor.java b/common/queue/src/main/java/org/thingsboard/server/queue/notification/NotificationRuleProcessor.java similarity index 93% rename from common/message/src/main/java/org/thingsboard/server/common/msg/notification/NotificationRuleProcessor.java rename to common/queue/src/main/java/org/thingsboard/server/queue/notification/NotificationRuleProcessor.java index 380773c1b0..1fa9d82863 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/notification/NotificationRuleProcessor.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/notification/NotificationRuleProcessor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.msg.notification; +package org.thingsboard.server.queue.notification; import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/notification/RemoteNotificationRuleProcessor.java b/common/queue/src/main/java/org/thingsboard/server/queue/notification/RemoteNotificationRuleProcessor.java index 85598b7318..3c3988389f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/notification/RemoteNotificationRuleProcessor.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/notification/RemoteNotificationRuleProcessor.java @@ -20,7 +20,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/DefaultApiLimitService.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/DefaultApiLimitService.java index d58c91c1c7..d5d19aa453 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/DefaultApiLimitService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/DefaultApiLimitService.java @@ -16,7 +16,6 @@ package org.thingsboard.server.dao.usagerecord; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.CustomerId; @@ -25,8 +24,6 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityTypeFilter; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; -import org.thingsboard.server.common.msg.notification.trigger.EntitiesLimitTrigger; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; @@ -36,8 +33,6 @@ public class DefaultApiLimitService implements ApiLimitService { private final EntityService entityService; private final TbTenantProfileCache tenantProfileCache; - @Autowired(required = false) - private NotificationRuleProcessor notificationRuleProcessor; @Override public boolean checkEntitiesLimit(TenantId tenantId, EntityType entityType) { @@ -47,14 +42,6 @@ public class DefaultApiLimitService implements ApiLimitService { EntityTypeFilter filter = new EntityTypeFilter(); filter.setEntityType(entityType); long currentCount = entityService.countEntitiesByQuery(tenantId, new CustomerId(EntityId.NULL_UUID), new EntityCountQuery(filter)); - if (notificationRuleProcessor != null) { - notificationRuleProcessor.process(EntitiesLimitTrigger.builder() - .tenantId(tenantId) - .entityType(entityType) - .currentCount(currentCount) - .limit(limit) - .build()); - } return currentCount < limit; } else { return true;