diff --git a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java index f90c90df54..b3fe76f154 100644 --- a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java +++ b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java @@ -208,9 +208,26 @@ public class DefaultEntityQueryService implements EntityQueryService { @Override public long countAlarmsByQuery(SecurityUser securityUser, AlarmCountQuery query) { + if (query.getEntityFilter() != null) { + EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query); + PageData entities = entityService.findEntityDataByQuery(securityUser.getTenantId(), + securityUser.getCustomerId(), entityDataQuery); + if (entities.getTotalElements() > 0) { + List entityIds = entities.getData().stream().map(EntityData::getEntityId).toList(); + return alarmService.countAlarmsByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query, entityIds); + } else { + return 0; + } + } return alarmService.countAlarmsByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query); } + private EntityDataQuery buildEntityDataQuery(AlarmCountQuery query) { + EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null, + new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, ModelConstants.CREATED_TIME_PROPERTY))); + return new EntityDataQuery(query.getEntityFilter(), edpl, null, null, query.getKeyFilters()); + } + private EntityDataQuery buildEntityDataQuery(AlarmDataQuery query) { EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder(); EntityDataSortOrder entitiesSortOrder; @@ -220,7 +237,7 @@ public class DefaultEntityQueryService implements EntityQueryService { entitiesSortOrder = sortOrder; } EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null, entitiesSortOrder); - return new EntityDataQuery(query.getEntityFilter(), edpl, query.getEntityFields(), query.getLatestValues(), query.getKeyFilters()); + return new EntityDataQuery(query.getEntityFilter(), edpl, null, null, query.getKeyFilters()); } @Override diff --git a/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java index 26c02e8704..bf1f9c3e82 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java @@ -36,10 +36,14 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmCountQuery; +import org.thingsboard.server.common.data.query.AlarmData; +import org.thingsboard.server.common.data.query.AlarmDataPageLink; +import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.DeviceTypeFilter; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValueSourceType; @@ -230,6 +234,60 @@ public class EntityQueryControllerTest extends AbstractControllerTest { testCountAlarmsByQuery(alarms); } + @Test + public void testTenantCountAlarmsWithEntityFilter() throws Exception { + loginTenantAdmin(); + List devices = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + Device device = new Device(); + device.setName("Device" + i); + device.setType("default"); + device.setLabel("testLabel" + (int) (Math.random() * 1000)); + Device savedDevice = doPost("/api/device", device, Device.class); + devices.add(savedDevice); + Thread.sleep(1); + + Alarm alarm = new Alarm(); + alarm.setOriginator(savedDevice.getId()); + alarm.setType("alarm" + i); + alarm.setSeverity(AlarmSeverity.WARNING); + doPost("/api/alarm", alarm, Alarm.class); + Thread.sleep(1); + } + + List assets = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + Asset asset = new Asset(); + asset.setName("Asset" + i); + asset.setType("default"); + asset.setLabel("testLabel" + (int) (Math.random() * 1000)); + Asset savedAsset = doPost("/api/asset", asset, Asset.class); + assets.add(savedAsset); + Thread.sleep(1); + + Alarm alarm = new Alarm(); + alarm.setOriginator(savedAsset.getId()); + alarm.setType("alarm" + i); + alarm.setSeverity(AlarmSeverity.WARNING); + doPost("/api/alarm", alarm, Alarm.class); + Thread.sleep(1); + } + + EntityTypeFilter assetTypeFilter = new EntityTypeFilter(); + assetTypeFilter.setEntityType(EntityType.ASSET); + AlarmCountQuery assetAlarmQuery = new AlarmCountQuery(assetTypeFilter); + + Long assetAlamCount = doPostWithResponse("/api/alarmsQuery/count", assetAlarmQuery, Long.class); + Assert.assertEquals(assets.size(), assetAlamCount.longValue()); + + KeyFilter nameFilter = buildStringKeyFilter(EntityKeyType.ENTITY_FIELD, "name", StringFilterPredicate.StringOperation.STARTS_WITH, "Asset1"); + List keyFilters = Collections.singletonList(nameFilter); + AlarmCountQuery filteredAssetAlarmQuery = new AlarmCountQuery(assetTypeFilter, keyFilters); + + Long filteredAssetAlamCount = doPostWithResponse("/api/alarmsQuery/count", filteredAssetAlarmQuery, Long.class); + Assert.assertEquals(1, filteredAssetAlamCount.longValue()); + } + @Test public void testCustomerCountAlarmsByQuery() throws Exception { loginTenantAdmin(); @@ -259,6 +317,213 @@ public class EntityQueryControllerTest extends AbstractControllerTest { testCountAlarmsByQuery(alarms); } + @Test + public void testCustomerCountAlarmsWithEntityFilter() throws Exception { + loginTenantAdmin(); + List devices = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + Device device = new Device(); + device.setCustomerId(customerId); + device.setName("Device" + i); + device.setType("default"); + device.setLabel("testLabel" + (int) (Math.random() * 1000)); + devices.add(doPost("/api/device", device, Device.class)); + Thread.sleep(1); + } + + List assets = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + Asset asset = new Asset(); + asset.setCustomerId(customerId); + asset.setName("Asset" + i); + asset.setType("default"); + asset.setLabel("testLabel" + (int) (Math.random() * 1000)); + assets.add(doPost("/api/asset", asset, Asset.class)); + Thread.sleep(1); + } + + loginCustomerUser(); + + for (int i = 0; i < devices.size(); i++) { + Alarm alarm = new Alarm(); + alarm.setCustomerId(customerId); + alarm.setOriginator(devices.get(i).getId()); + alarm.setType("alarm" + i); + alarm.setSeverity(AlarmSeverity.WARNING); + doPost("/api/alarm", alarm, Alarm.class); + Thread.sleep(1); + } + + for (int i = 0; i < assets.size(); i++) { + Alarm alarm = new Alarm(); + alarm.setCustomerId(customerId); + alarm.setOriginator(assets.get(i).getId()); + alarm.setType("alarm" + i); + alarm.setSeverity(AlarmSeverity.WARNING); + doPost("/api/alarm", alarm, Alarm.class); + Thread.sleep(1); + } + + EntityTypeFilter assetTypeFilter = new EntityTypeFilter(); + assetTypeFilter.setEntityType(EntityType.ASSET); + AlarmCountQuery assetAlarmQuery = new AlarmCountQuery(assetTypeFilter); + + Long assetAlamCount = doPostWithResponse("/api/alarmsQuery/count", assetAlarmQuery, Long.class); + Assert.assertEquals(10, assetAlamCount.longValue()); + + KeyFilter nameFilter = buildStringKeyFilter(EntityKeyType.ENTITY_FIELD, "name", StringFilterPredicate.StringOperation.STARTS_WITH, "Asset1"); + List keyFilters = Collections.singletonList(nameFilter); + AlarmCountQuery filteredAssetAlarmQuery = new AlarmCountQuery(assetTypeFilter, keyFilters); + + Long filteredAssetAlamCount = doPostWithResponse("/api/alarmsQuery/count", filteredAssetAlarmQuery, Long.class); + Assert.assertEquals(1, filteredAssetAlamCount.longValue()); + } + + @Test + public void testFindTenantAlarmsWithEntityFilter() throws Exception { + loginTenantAdmin(); + List devices = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + Device device = new Device(); + device.setCustomerId(customerId); + device.setName("Device" + i); + device.setType("default"); + device.setLabel("testLabel" + (int) (Math.random() * 1000)); + devices.add(doPost("/api/device", device, Device.class)); + Thread.sleep(1); + } + + List assets = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + Asset asset = new Asset(); + asset.setCustomerId(customerId); + asset.setName("Asset" + i); + asset.setType("default"); + asset.setLabel("testLabel" + (int) (Math.random() * 1000)); + assets.add(doPost("/api/asset", asset, Asset.class)); + Thread.sleep(1); + } + + for (int i = 0; i < devices.size(); i++) { + Alarm alarm = new Alarm(); + alarm.setOriginator(devices.get(i).getId()); + alarm.setType("alarm" + i); + alarm.setSeverity(AlarmSeverity.WARNING); + doPost("/api/alarm", alarm, Alarm.class); + Thread.sleep(1); + } + + List assetAlarmTypes = new ArrayList<>(); + for (int i = 0; i < assets.size(); i++) { + Alarm alarm = new Alarm(); + alarm.setOriginator(assets.get(i).getId()); + String type = "asset alarm" + i; + alarm.setType(type); + assetAlarmTypes.add(type); + alarm.setSeverity(AlarmSeverity.WARNING); + doPost("/api/alarm", alarm, Alarm.class); + Thread.sleep(1); + } + + AlarmDataPageLink pageLink = new AlarmDataPageLink(); + pageLink.setPage(0); + pageLink.setPageSize(100); + pageLink.setSortOrder(new EntityDataSortOrder(new EntityKey(EntityKeyType.ALARM_FIELD, "assignee"))); + + List alarmFields = new ArrayList<>(); + alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, "type")); + + EntityTypeFilter assetTypeFilter = new EntityTypeFilter(); + assetTypeFilter.setEntityType(EntityType.ASSET); + AlarmDataQuery assetAlarmQuery = new AlarmDataQuery(assetTypeFilter, pageLink, null, null, null, alarmFields); + + PageData alarmPageData = doPostWithTypedResponse("/api/alarmsQuery/find", assetAlarmQuery, new TypeReference<>() { + }); + Assert.assertEquals(10, alarmPageData.getTotalElements()); + List retrievedAlarmTypes = alarmPageData.getData().stream().map(Alarm::getType).toList(); + assertThat(retrievedAlarmTypes).containsExactlyInAnyOrderElementsOf(assetAlarmTypes); + + KeyFilter nameFilter = buildStringKeyFilter(EntityKeyType.ENTITY_FIELD, "name", StringFilterPredicate.StringOperation.STARTS_WITH, "Asset1"); + List keyFilters = Collections.singletonList(nameFilter); + AlarmDataQuery filteredAssetAlarmQuery = new AlarmDataQuery(assetTypeFilter, pageLink, null, null, keyFilters, alarmFields); + PageData filteredAssetAlamData = doPostWithTypedResponse("/api/alarmsQuery/find", filteredAssetAlarmQuery, new TypeReference<>() { + }); + Assert.assertEquals(1, filteredAssetAlamData.getTotalElements()); + } + + @Test + public void testFindCustomerAlarmsWithEntityFilter() throws Exception { + loginTenantAdmin(); + List devices = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + Device device = new Device(); + device.setCustomerId(customerId); + device.setName("Device" + i); + device.setType("default"); + device.setLabel("testLabel" + (int) (Math.random() * 1000)); + devices.add(doPost("/api/device", device, Device.class)); + Thread.sleep(1); + } + + List assets = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + Asset asset = new Asset(); + asset.setCustomerId(customerId); + asset.setName("Asset" + i); + asset.setType("default"); + asset.setLabel("testLabel" + (int) (Math.random() * 1000)); + assets.add(doPost("/api/asset", asset, Asset.class)); + Thread.sleep(1); + } + + loginCustomerUser(); + + for (int i = 0; i < devices.size(); i++) { + Alarm alarm = new Alarm(); + alarm.setCustomerId(customerId); + alarm.setOriginator(devices.get(i).getId()); + alarm.setType("alarm" + i); + alarm.setSeverity(AlarmSeverity.WARNING); + doPost("/api/alarm", alarm, Alarm.class); + Thread.sleep(1); + } + + List assetAlarmTypes = new ArrayList<>(); + for (int i = 0; i < assets.size(); i++) { + Alarm alarm = new Alarm(); + alarm.setCustomerId(customerId); + alarm.setOriginator(assets.get(i).getId()); + String type = "asset alarm" + i; + alarm.setType(type); + assetAlarmTypes.add(type); + alarm.setSeverity(AlarmSeverity.WARNING); + doPost("/api/alarm", alarm, Alarm.class); + Thread.sleep(1); + } + + AlarmDataPageLink pageLink = new AlarmDataPageLink(); + pageLink.setPage(0); + pageLink.setPageSize(100); + pageLink.setSortOrder(new EntityDataSortOrder(new EntityKey(EntityKeyType.ALARM_FIELD, "assignee"))); + + EntityTypeFilter assetTypeFilter = new EntityTypeFilter(); + assetTypeFilter.setEntityType(EntityType.ASSET); + AlarmDataQuery assetAlarmQuery = new AlarmDataQuery(assetTypeFilter, pageLink, null, null, null, Collections.emptyList()); + + PageData alarmPageData = doPostWithTypedResponse("/api/alarmsQuery/find", assetAlarmQuery, new TypeReference<>() { + }); + Assert.assertEquals(10, alarmPageData.getTotalElements()); + List retrievedAlarmTypes = alarmPageData.getData().stream().map(Alarm::getType).toList(); + assertThat(retrievedAlarmTypes).containsExactlyInAnyOrderElementsOf(assetAlarmTypes); + + KeyFilter nameFilter = buildStringKeyFilter(EntityKeyType.ENTITY_FIELD, "name", StringFilterPredicate.StringOperation.STARTS_WITH, "Asset1"); + List keyFilters = Collections.singletonList(nameFilter); + AlarmDataQuery filteredAssetAlarmQuery = new AlarmDataQuery(assetTypeFilter, pageLink, null, null, keyFilters, Collections.emptyList()); + PageData filteredAssetAlamData = doPostWithTypedResponse("/api/alarmsQuery/find", filteredAssetAlarmQuery, new TypeReference<>() { + }); + Assert.assertEquals(1, filteredAssetAlamData.getTotalElements()); + } + private void testCountAlarmsByQuery(List alarms) throws Exception { AlarmCountQuery countQuery = new AlarmCountQuery(); @@ -913,4 +1178,14 @@ public class EntityQueryControllerTest extends AbstractControllerTest { return numericFilter; } + private KeyFilter buildStringKeyFilter(EntityKeyType entityKeyType, String name, StringFilterPredicate.StringOperation operation, String value) { + KeyFilter nameFilter = new KeyFilter(); + nameFilter.setKey(new EntityKey(entityKeyType, name)); + StringFilterPredicate predicate = new StringFilterPredicate(); + predicate.setOperation(operation); + predicate.setValue(FilterPredicateValue.fromString(value)); + nameFilter.setPredicate(predicate); + return nameFilter; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmCountQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmCountQuery.java index 71a8e8ef33..1ab7ef5b34 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmCountQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmCountQuery.java @@ -45,4 +45,8 @@ public class AlarmCountQuery extends EntityCountQuery { super(entityFilter); } + public AlarmCountQuery(EntityFilter entityFilter, List keyFilters) { + super(entityFilter, keyFilters); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java index 31547246c6..c63682b899 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmData.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.common.data.query; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.EqualsAndHashCode; import lombok.Getter; import org.thingsboard.server.common.data.alarm.Alarm; @@ -34,6 +36,12 @@ public class AlarmData extends AlarmInfo { @Getter private final Map> latest; + @JsonCreator + public AlarmData(@JsonProperty("entityId") EntityId entityId, @JsonProperty("latest") Map> latest) { + this.entityId = entityId; + this.latest = latest; + } + public AlarmData(AlarmInfo main, AlarmData prototype) { super(main); this.entityId = prototype.entityId;