diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index 3fc314b632..78d7f01404 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -33,7 +33,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.socket.CloseStatus; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; @@ -41,7 +41,6 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.ComparisonTsValue; -import org.thingsboard.server.common.data.query.OriginatorAlarmFilter; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityKey; @@ -55,17 +54,16 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.executors.DbCallbackExecutorService; -import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggHistoryCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggKey; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggTimeSeriesCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusCmd; -import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; @@ -74,7 +72,6 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.GetTsCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; -import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate; import java.util.ArrayList; import java.util.Arrays; @@ -83,7 +80,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; @@ -430,13 +426,23 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc long start = System.currentTimeMillis(); ctx.fetchData(); long end = System.currentTimeMillis(); - stats.getAlarmQueryInvocationCnt().incrementAndGet(); - stats.getAlarmQueryTimeSpent().addAndGet(end - start); - TbAlarmCountSubCtx finalCtx = ctx; - ScheduledFuture task = scheduler.scheduleWithFixedDelay( - () -> refreshDynamicQuery(finalCtx), - dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS); - finalCtx.setRefreshTask(task); + stats.getRegularQueryInvocationCnt().incrementAndGet(); + stats.getRegularQueryTimeSpent().addAndGet(end - start); + Set entitiesIds = ctx.getEntitiesIds(); + ctx.cancelTasks(); + ctx.clearAlarmSubscriptions(); + if (entitiesIds != null && entitiesIds.isEmpty()) { + AlarmCountUpdate update = new AlarmCountUpdate(cmd.getCmdId(), 0); + ctx.sendWsMsg(update); + } else { + ctx.doFetchAlarmCount(); + ctx.createAlarmSubscriptions(); + TbAlarmCountSubCtx finalCtx = ctx; + ScheduledFuture task = scheduler.scheduleWithFixedDelay( + () -> refreshDynamicQuery(finalCtx), + dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS); + finalCtx.setRefreshTask(task); + } } else { log.debug("[{}][{}] Received duplicate command: {}", session.getSessionId(), cmd.getCmdId(), cmd); } @@ -555,7 +561,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc private TbAlarmCountSubCtx createSubCtx(WebSocketSessionRef sessionRef, AlarmCountCmd cmd) { Map sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new ConcurrentHashMap<>()); TbAlarmCountSubCtx ctx = new TbAlarmCountSubCtx(serviceId, wsService, entityService, localSubscriptionService, - attributesService, stats, alarmService, sessionRef, cmd.getCmdId()); + attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription, maxAlarmQueriesPerRefreshInterval); if (cmd.getQuery() != null) { ctx.setAndResolveQuery(cmd.getQuery()); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmCountSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmCountSubCtx.java index ae3c2e0ccd..6689c11c42 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmCountSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmCountSubCtx.java @@ -19,49 +19,152 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; +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.EntityData; +import org.thingsboard.server.common.data.query.EntityDataPageLink; +import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.common.data.query.EntityDataSortOrder; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + @Slf4j @ToString(callSuper = true) public class TbAlarmCountSubCtx extends TbAbstractEntityQuerySubCtx { private final AlarmService alarmService; + protected final Map subToEntityIdMap; + + @Getter + private LinkedHashSet entitiesIds; + + private final int maxEntitiesPerAlarmSubscription; + + private final int maxAlarmQueriesPerRefreshInterval; + @Getter @Setter private volatile int result; + @Getter + @Setter + private boolean tooManyEntities; + + private int alarmCountInvocationAttempts; + public TbAlarmCountSubCtx(String serviceId, WebSocketService wsService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, SubscriptionServiceStatistics stats, AlarmService alarmService, - WebSocketSessionRef sessionRef, int cmdId) { + WebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerAlarmSubscription, int maxAlarmQueriesPerRefreshInterval) { super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); this.alarmService = alarmService; + this.subToEntityIdMap = new ConcurrentHashMap<>(); + this.maxEntitiesPerAlarmSubscription = maxEntitiesPerAlarmSubscription; + this.maxAlarmQueriesPerRefreshInterval = maxAlarmQueriesPerRefreshInterval; + this.entitiesIds = null; + } + + @Override + public void clearSubscriptions() { + clearAlarmSubscriptions(); } @Override public void fetchData() { - result = (int) alarmService.countAlarmsByQuery(getTenantId(), getCustomerId(), query); - sendWsMsg(new AlarmCountUpdate(cmdId, result)); + resetInvocationCounter(); + if (query.getEntityFilter() != null) { + entitiesIds = new LinkedHashSet<>(); + log.trace("[{}] Fetching data: {}", cmdId, alarmCountInvocationAttempts); + PageData data = entityService.findEntityDataByQuery(getTenantId(), getCustomerId(), buildEntityDataQuery()); + entitiesIds.clear(); + tooManyEntities = data.hasNext(); + for (EntityData entityData : data.getData()) { + entitiesIds.add(entityData.getEntityId()); + } + } } @Override protected void update() { - int newCount = (int) alarmService.countAlarmsByQuery(getTenantId(), getCustomerId(), query); - if (newCount != result) { - result = newCount; - sendWsMsg(new AlarmCountUpdate(cmdId, result)); - } + resetInvocationCounter(); + fetchAlarmCount(); } @Override public boolean isDynamic() { return true; } + + public void fetchAlarmCount() { + alarmCountInvocationAttempts++; + log.trace("[{}] Fetching alarms: {}", cmdId, alarmCountInvocationAttempts); + if (alarmCountInvocationAttempts <= maxAlarmQueriesPerRefreshInterval) { + int newCount = (int) alarmService.countAlarmsByQuery(getTenantId(), getCustomerId(), query, entitiesIds); + if (newCount != result) { + result = newCount; + sendWsMsg(new AlarmCountUpdate(cmdId, result)); + } + } else { + log.trace("[{}] Ignore alarm count fetch due to rate limit: [{}] of maximum [{}]", cmdId, alarmCountInvocationAttempts, maxAlarmQueriesPerRefreshInterval); + } + } + + public void doFetchAlarmCount() { + result = (int) alarmService.countAlarmsByQuery(getTenantId(), getCustomerId(), query, entitiesIds); + sendWsMsg(new AlarmCountUpdate(cmdId, result)); + } + + private EntityDataQuery buildEntityDataQuery() { + 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 void resetInvocationCounter() { + alarmCountInvocationAttempts = 0; + } + + public void createAlarmSubscriptions() { + for (EntityId entityId : entitiesIds) { + createAlarmSubscriptionForEntity(entityId); + } + } + + private void createAlarmSubscriptionForEntity(EntityId entityId) { + int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet(); + subToEntityIdMap.put(subIdx, entityId); + log.trace("[{}][{}][{}] Creating alarms subscription for [{}] ", serviceId, cmdId, subIdx, entityId); + TbAlarmsSubscription subscription = TbAlarmsSubscription.builder() + .serviceId(serviceId) + .sessionId(sessionRef.getSessionId()) + .subscriptionId(subIdx) + .tenantId(sessionRef.getSecurityCtx().getTenantId()) + .entityId(entityId) + .updateProcessor((sub, update) -> fetchAlarmCount()) + .build(); + localSubscriptionService.addSubscription(subscription, sessionRef); + } + + public void clearAlarmSubscriptions() { + if (subToEntityIdMap != null) { + for (Integer subId : subToEntityIdMap.keySet()) { + localSubscriptionService.cancelSubscription(getTenantId(), getSessionId(), subId); + } + subToEntityIdMap.clear(); + } + } + } diff --git a/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java b/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java index c33e9f7202..69d50c61d1 100644 --- a/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java @@ -324,6 +324,83 @@ public class WebsocketApiTest extends AbstractControllerTest { Assert.assertEquals(1, update.getCount()); } + @Test + public void testAlarmCountWsCmdWithSingleEntityFilter() throws Exception { + loginTenantAdmin(); + + SingleEntityFilter singleEntityFilter = new SingleEntityFilter(); + singleEntityFilter.setSingleEntity(tenantId); + AlarmCountQuery alarmCountQuery = new AlarmCountQuery(singleEntityFilter); + AlarmCountCmd cmd1 = new AlarmCountCmd(1, alarmCountQuery); + + getWsClient().send(cmd1); + + AlarmCountUpdate update = getWsClient().parseAlarmCountReply(getWsClient().waitForReply()); + Assert.assertEquals(1, update.getCmdId()); + Assert.assertEquals(0, update.getCount()); + + //create alarm, check count = 1 + getWsClient().registerWaitForUpdate(); + + Alarm alarm = new Alarm(); + alarm.setOriginator(tenantId); + alarm.setType("TEST ALARM"); + alarm.setSeverity(AlarmSeverity.WARNING); + alarm = doPost("/api/alarm", alarm, Alarm.class); + + update = getWsClient().parseAlarmCountReply(getWsClient().waitForUpdate()); + Assert.assertEquals(1, update.getCmdId()); + Assert.assertEquals(1, update.getCount()); + + // set wrong entity id in filter, check count = 0 + singleEntityFilter.setSingleEntity(tenantAdminUserId); + AlarmCountCmd cmd3 = new AlarmCountCmd(2, alarmCountQuery); + + getWsClient().send(cmd3); + + update = getWsClient().parseAlarmCountReply(getWsClient().waitForReply()); + Assert.assertEquals(2, update.getCmdId()); + Assert.assertEquals(0, update.getCount()); + } + + @Test + public void testAlarmCountWsCmdWithDeviceType() throws Exception { + loginTenantAdmin(); + + DeviceTypeFilter deviceTypeFilter = new DeviceTypeFilter(); + deviceTypeFilter.setDeviceTypes(List.of("default")); + AlarmCountQuery alarmCountQuery = new AlarmCountQuery(deviceTypeFilter); + AlarmCountCmd cmd1 = new AlarmCountCmd(1, alarmCountQuery); + + getWsClient().send(cmd1); + + AlarmCountUpdate update = getWsClient().parseAlarmCountReply(getWsClient().waitForReply()); + Assert.assertEquals(1, update.getCmdId()); + Assert.assertEquals(0, update.getCount()); + + getWsClient().registerWaitForUpdate(); + + Alarm alarm = new Alarm(); + alarm.setOriginator(device.getId()); + alarm.setType("TEST ALARM"); + alarm.setSeverity(AlarmSeverity.WARNING); + + alarm = doPost("/api/alarm", alarm, Alarm.class); + + update = getWsClient().parseAlarmCountReply(getWsClient().waitForUpdate()); + Assert.assertEquals(1, update.getCmdId()); + Assert.assertEquals(1, update.getCount()); + + deviceTypeFilter.setDeviceTypes(List.of("non-existing")); + AlarmCountCmd cmd3 = new AlarmCountCmd(3, alarmCountQuery); + + getWsClient().send(cmd3); + + update = getWsClient().parseAlarmCountReply(getWsClient().waitForReply()); + Assert.assertEquals(3, update.getCmdId()); + Assert.assertEquals(0, update.getCount()); + } + @Test public void testAlarmStatusWsCmd() throws Exception { loginTenantAdmin(); @@ -372,17 +449,18 @@ public class WebsocketApiTest extends AbstractControllerTest { doPost("/api/alarm", alarm2, Alarm.class); - AlarmStatusUpdate alarmStatusUpdate3 = JacksonUtil.fromString(getWsClient().waitForReply(), AlarmStatusUpdate.class); + AlarmStatusUpdate alarmStatusUpdate3 = JacksonUtil.fromString(getWsClient().waitForUpdate(), AlarmStatusUpdate.class); Assert.assertEquals(1, alarmStatusUpdate3.getCmdId()); Assert.assertTrue(alarmStatusUpdate3.isActive()); //change severity + getWsClient().registerWaitForUpdate(); alarm2.setSeverity(AlarmSeverity.MAJOR); Alarm updatedAlarm = doPost("/api/alarm", alarm2, Alarm.class); Assert.assertNotNull(updatedAlarm); Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity()); - AlarmStatusUpdate alarmStatusUpdate4 = JacksonUtil.fromString(getWsClient().waitForReply(), AlarmStatusUpdate.class); + AlarmStatusUpdate alarmStatusUpdate4 = JacksonUtil.fromString(getWsClient().waitForUpdate(), AlarmStatusUpdate.class); Assert.assertEquals(1, alarmStatusUpdate4.getCmdId()); Assert.assertFalse(alarmStatusUpdate4.isActive()); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index 7792d05a4c..7be61d15d5 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -118,6 +118,8 @@ public interface AlarmService extends EntityDaoService { long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query); + long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query, Collection orderedEntityIds); + PageData findAlarmTypesByTenantId(TenantId tenantId, PageLink pageLink); List findActiveOriginatorAlarms(TenantId tenantId, OriginatorAlarmFilter originatorAlarmFilter, int limit); 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 103bd90eac..71a8e8ef33 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 @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.query; import lombok.AllArgsConstructor; import lombok.Builder; -import lombok.Getter; +import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; @@ -29,7 +29,7 @@ import java.util.List; @Builder @NoArgsConstructor @AllArgsConstructor -@Getter +@Data @ToString public class AlarmCountQuery extends EntityCountQuery { private long startTs; @@ -40,4 +40,9 @@ public class AlarmCountQuery extends EntityCountQuery { private List severityList; private boolean searchPropagatedAlarms; private UserId assigneeId; + + public AlarmCountQuery(EntityFilter entityFilter) { + super(entityFilter); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index b0439b6407..0351490f70 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -106,7 +106,7 @@ public interface AlarmDao extends Dao { AlarmApiCallResult unassignAlarm(TenantId tenantId, AlarmId alarmId, long unassignTime); - long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query); + long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query, Collection orderedEntityIds); PageData findTenantAlarmTypes(UUID tenantId, PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index fcfd5e0a8d..23e05e1bfa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -351,8 +351,13 @@ public class BaseAlarmService extends AbstractCachedEntityService orderedEntityIds) { validateId(tenantId, id -> INCORRECT_TENANT_ID + id); - return alarmDao.countAlarmsByQuery(tenantId, customerId, query); + return alarmDao.countAlarmsByQuery(tenantId, customerId, query, orderedEntityIds); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index 1672ab4b3a..34b8fc91e1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -415,8 +415,8 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } @Override - public long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query) { - return alarmQueryRepository.countAlarmsByQuery(tenantId, customerId, query); + public long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query, Collection orderedEntityIds) { + return alarmQueryRepository.countAlarmsByQuery(tenantId, customerId, query, orderedEntityIds); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmQueryRepository.java index adbb3fc200..4380186786 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmQueryRepository.java @@ -30,6 +30,6 @@ public interface AlarmQueryRepository { PageData findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection orderedEntityIds); - long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query); + long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query, Collection orderedEntityIds); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index 957e52142a..c990a2dba4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -44,6 +44,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.UUID; import java.util.stream.Collectors; @Repository @@ -314,25 +315,41 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { } @Override - public long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query) { + public long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query, Collection orderedEntityIds) { QueryContext ctx = new QueryContext(new QuerySecurityContext(tenantId, null, EntityType.ALARM)); if (query.isSearchPropagatedAlarms()) { ctx.append("select count(distinct(a.id)) from alarm_info a "); ctx.append(JOIN_ENTITY_ALARMS); - ctx.append("where a.tenant_id = :tenantId and ea.tenant_id = :tenantId"); - ctx.addUuidParameter("tenantId", tenantId.getId()); - if (customerId != null && !customerId.isNullUid()) { - ctx.append(" and a.customer_id = :customerId and ea.customer_id = :customerId"); - ctx.addUuidParameter("customerId", customerId.getId()); + if (orderedEntityIds != null) { + if (orderedEntityIds.isEmpty()) { + return 0; + } + ctx.addUuidListParameter("entity_filter_entity_ids", orderedEntityIds.stream().map(EntityId::getId).collect(Collectors.toList())); + ctx.append("where ea.entity_id in (:entity_filter_entity_ids)"); + } else { + ctx.append("where a.tenant_id = :tenantId and ea.tenant_id = :tenantId"); + ctx.addUuidParameter("tenantId", tenantId.getId()); + if (customerId != null && !customerId.isNullUid()) { + ctx.append(" and a.customer_id = :customerId and ea.customer_id = :customerId"); + ctx.addUuidParameter("customerId", customerId.getId()); + } } } else { ctx.append("select count(id) from alarm_info a "); - ctx.append("where a.tenant_id = :tenantId"); - ctx.addUuidParameter("tenantId", tenantId.getId()); - if (customerId != null && !customerId.isNullUid()) { - ctx.append(" and a.customer_id = :customerId"); - ctx.addUuidParameter("customerId", customerId.getId()); + if (orderedEntityIds != null) { + if (orderedEntityIds.isEmpty()) { + return 0; + } + ctx.addUuidListParameter("entity_filter_entity_ids", orderedEntityIds.stream().map(EntityId::getId).collect(Collectors.toList())); + ctx.append("where a.originator_id in (:entity_filter_entity_ids)"); + } else { + ctx.append("where a.tenant_id = :tenantId"); + ctx.addUuidParameter("tenantId", tenantId.getId()); + if (customerId != null && !customerId.isNullUid()) { + ctx.append(" and a.customer_id = :customerId"); + ctx.addUuidParameter("customerId", customerId.getId()); + } } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java index c82c6bd5f6..849528b091 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java @@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; @@ -48,6 +49,7 @@ import org.thingsboard.server.common.data.query.DeviceTypeFilter; import org.thingsboard.server.common.data.query.EntityDataSortOrder; import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.EntityListFilter; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.security.Authority; @@ -936,4 +938,53 @@ public class AlarmServiceTest extends AbstractServiceTest { Assert.assertEquals(0, alarms.getData().size()); } + @Test + public void testCountAlarmsForEntities() throws ExecutionException, InterruptedException { + AssetId parentId = new AssetId(Uuids.timeBased()); + AssetId childId = new AssetId(Uuids.timeBased()); + + EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); + + Assert.assertTrue(relationService.saveRelationAsync(tenantId, relation).get()); + + long ts = System.currentTimeMillis(); + AlarmApiCallResult result = alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.builder() + .tenantId(tenantId) + .originator(childId) + .type(TEST_ALARM) + .severity(AlarmSeverity.CRITICAL) + .startTs(ts).build()); + AlarmInfo created = result.getAlarm(); + created.setPropagate(true); + result = alarmService.updateAlarm(AlarmUpdateRequest.fromAlarm(created)); + created = result.getAlarm(); + + EntityListFilter entityListFilter = new EntityListFilter(); + entityListFilter.setEntityList(List.of(childId.getId().toString(), parentId.getId().toString())); + entityListFilter.setEntityType(EntityType.ASSET); + AlarmCountQuery countQuery = new AlarmCountQuery(entityListFilter); + countQuery.setStartTs(0L); + countQuery.setEndTs(System.currentTimeMillis()); + + long alarmsCount = alarmService.countAlarmsByQuery(tenantId, null, countQuery, List.of(childId)); + Assert.assertEquals(1, alarmsCount); + + countQuery.setSearchPropagatedAlarms(true); + + alarmsCount = alarmService.countAlarmsByQuery(tenantId, null, countQuery, List.of(parentId)); + Assert.assertEquals(1, alarmsCount); + + created = alarmService.acknowledgeAlarm(tenantId, created.getId(), System.currentTimeMillis()).getAlarm(); + + countQuery.setStatusList(List.of(AlarmSearchStatus.UNACK)); + alarmsCount = alarmService.countAlarmsByQuery(tenantId, null, countQuery, List.of(childId)); + Assert.assertEquals(0, alarmsCount); + + alarmService.clearAlarm(tenantId, created.getId(), System.currentTimeMillis(), null); + + countQuery.setStatusList(List.of(AlarmSearchStatus.CLEARED)); + alarmsCount = alarmService.countAlarmsByQuery(tenantId, null, countQuery, List.of(childId)); + Assert.assertEquals(1, alarmsCount); + } + } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarm-count-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarm-count-basic-config.component.html index 2e7dc37581..59b137c6c0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarm-count-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarm-count-basic-config.component.html @@ -16,6 +16,15 @@ --> + +
alarm.filter
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarm-count-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarm-count-basic-config.component.ts index 20ceb9130a..de2959cb0d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarm-count-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarm-count-basic-config.component.ts @@ -69,6 +69,7 @@ export class AlarmCountBasicConfigComponent extends BasicWidgetConfigComponent { const settings: CountWidgetSettings = {...countDefaultSettings(true), ...(configData.config.settings || {})}; this.alarmCountWidgetConfigForm = this.fb.group({ alarmFilterConfig: [getAlarmFilterConfig(configData.config.datasources), []], + datasources: [configData.config.datasources, []], settings: [settings, []], @@ -81,6 +82,7 @@ export class AlarmCountBasicConfigComponent extends BasicWidgetConfigComponent { } protected prepareOutputConfig(config: any): WidgetConfigComponentData { + this.widgetConfig.config.datasources = config.datasources; setAlarmFilterConfig(config.alarmFilterConfig, this.widgetConfig.config.datasources); this.widgetConfig.config.settings = {...(this.widgetConfig.config.settings || {}), ...config.settings}; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html b/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html index 297f0c301d..0219872626 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html @@ -36,7 +36,7 @@ datasourceFormGroup.get('type').value === datasourceType.entity || datasourceFormGroup.get('type').value === datasourceType.entityCount || datasourceFormGroup.get('type').value === datasourceType.alarmCount ? datasourceFormGroup.get('type').value : ''"> - @@ -98,7 +98,7 @@