diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 723d094df8..8fa39aa8b3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -44,6 +44,8 @@ import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import java.util.UUID; + @RestController @RequestMapping("/api") public class AlarmController extends BaseController { @@ -155,6 +157,7 @@ public class AlarmController extends BaseController { @RequestParam(required = false) String sortOrder, @RequestParam(required = false) Long startTime, @RequestParam(required = false) Long endTime, + @RequestParam(required = false) String offset, @RequestParam(required = false) Boolean fetchOriginator ) throws ThingsboardException { checkParameter("EntityId", strEntityId); @@ -168,8 +171,12 @@ public class AlarmController extends BaseController { } checkEntityId(entityId, Operation.READ); TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + UUID idOffsetUuid = null; + if (StringUtils.isNotEmpty(offset)) { + idOffsetUuid = toUUID(offset); + } try { - return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); + return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator, idOffsetUuid)).get()); } catch (Exception e) { throw handleException(e); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java index 224ac6050a..995dc102e7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java @@ -22,6 +22,8 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TimePageLink; +import java.util.UUID; + /** * Created by ashvayka on 11.05.17. */ @@ -35,5 +37,6 @@ public class AlarmQuery { private AlarmSearchStatus searchStatus; private AlarmStatus status; private Boolean fetchOriginator; + private UUID idOffset; } 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 4ae5fb8ef2..b51523a3cb 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 @@ -300,7 +300,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ AlarmSeverity highestSeverity = null; AlarmQuery query; while (hasNext && AlarmSeverity.CRITICAL != highestSeverity) { - query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false); + query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false, null); PageData alarms = alarmDao.findAlarms(tenantId, query); if (alarms.hasNext()) { nextPageLink = nextPageLink.nextPageLink(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index 906d0f7bda..2f3c72d01b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -33,7 +33,7 @@ import java.util.List; @SqlDao public interface AlarmRepository extends CrudRepository { - @Query("SELECT a FROM AlarmEntity a WHERE a.originatorId = :originatorId AND a.type = :alarmType ORDER BY startTs DESC") + @Query("SELECT a FROM AlarmEntity a WHERE a.originatorId = :originatorId AND a.type = :alarmType ORDER BY a.startTs DESC") List findLatestByOriginatorAndType(@Param("originatorId") String originatorId, @Param("alarmType") String alarmType, Pageable pageable); @@ -48,6 +48,7 @@ public interface AlarmRepository extends CrudRepository { "AND re.fromType = :affectedEntityType " + "AND (:startId IS NULL OR a.id >= :startId) " + "AND (:endId IS NULL OR a.id <= :endId) " + + "AND (:idOffset IS NULL OR a.id < :idOffset) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%'))" + "OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%'))" + "OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%')))") @@ -57,6 +58,7 @@ public interface AlarmRepository extends CrudRepository { @Param("relationType") String relationType, @Param("startId") String startId, @Param("endId") String endId, + @Param("idOffset") String idOffset, @Param("searchText") String searchText, Pageable pageable); } 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 295c647297..ced07a1e1a 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 @@ -117,6 +117,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A relationType, startTimeToId(query.getPageLink().getStartTime()), endTimeToId(query.getPageLink().getEndTime()), + query.getIdOffset() != null ? UUIDConverter.fromTimeUUID(query.getIdOffset()) : null, Objects.toString(query.getPageLink().getTextSearch(), ""), DaoUtil.toPageable(query.getPageLink()) ) diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index 92df055f21..dde2459efc 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -190,6 +190,8 @@ export interface WidgetSubscriptionOptions { alarmSource?: Datasource; alarmSearchStatus?: AlarmSearchStatus; alarmsPollingInterval?: number; + alarmsMaxCountLoad?: number; + alarmsFetchSize?: number; datasources?: Array; targetDeviceAliasIds?: Array; targetDeviceIds?: Array; diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index bd78bf674e..28c3831266 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -93,6 +93,8 @@ export class WidgetSubscription implements IWidgetSubscription { } alarmsPollingInterval: number; + alarmsMaxCountLoad: number; + alarmsFetchSize: number; alarmSourceListener: AlarmSourceListener; loadingData: boolean; @@ -153,6 +155,10 @@ export class WidgetSubscription implements IWidgetSubscription { options.alarmSearchStatus : AlarmSearchStatus.ANY; this.alarmsPollingInterval = isDefined(options.alarmsPollingInterval) ? options.alarmsPollingInterval : 5000; + this.alarmsMaxCountLoad = isDefined(options.alarmsMaxCountLoad) ? + options.alarmsMaxCountLoad : 0; + this.alarmsFetchSize = isDefined(options.alarmsFetchSize) ? + options.alarmsFetchSize : 100; this.alarmSourceListener = null; this.alarms = []; this.originalTimewindow = null; @@ -712,6 +718,8 @@ export class WidgetSubscription implements IWidgetSubscription { alarmSource: this.alarmSource, alarmSearchStatus: this.alarmSearchStatus, alarmsPollingInterval: this.alarmsPollingInterval, + alarmsMaxCountLoad: this.alarmsMaxCountLoad, + alarmsFetchSize: this.alarmsFetchSize, alarmsUpdated: alarms => this.alarmsUpdated(alarms) }; this.alarms = null; diff --git a/ui-ngx/src/app/core/http/alarm.service.ts b/ui-ngx/src/app/core/http/alarm.service.ts index e06a303f5d..3e2bf58285 100644 --- a/ui-ngx/src/app/core/http/alarm.service.ts +++ b/ui-ngx/src/app/core/http/alarm.service.ts @@ -38,12 +38,15 @@ import { Direction, SortOrder } from '@shared/models/page/sort-order'; import { concatMap, expand, map, toArray } from 'rxjs/operators'; import { EMPTY } from 'rxjs'; import Timeout = NodeJS.Timeout; +import { isDefined } from '@core/utils'; interface AlarmSourceListenerQuery { entityType: EntityType; entityId: string; alarmSearchStatus: AlarmSearchStatus; alarmStatus: AlarmStatus; + alarmsMaxCountLoad: number; + alarmsFetchSize: number; fetchOriginator?: boolean; limit?: number; interval?: number; @@ -58,6 +61,8 @@ export interface AlarmSourceListener { alarmSource: Datasource; alarmsPollingInterval: number; alarmSearchStatus: AlarmSearchStatus; + alarmsMaxCountLoad: number; + alarmsFetchSize: number; alarmsUpdated: (alarms: Array) => void; lastUpdateTs?: number; alarmsQuery?: AlarmSourceListenerQuery; @@ -132,7 +137,9 @@ export class AlarmService { entityType: alarmSource.entityType, entityId: alarmSource.entityId, alarmSearchStatus: alarmSourceListener.alarmSearchStatus, - alarmStatus: null + alarmStatus: null, + alarmsMaxCountLoad: alarmSourceListener.alarmsMaxCountLoad, + alarmsFetchSize: alarmSourceListener.alarmsFetchSize }; const originatorKeys = alarmSource.dataKeys.filter(dataKey => dataKey.name.toLocaleLowerCase().includes('originator')); if (originatorKeys.length) { @@ -186,32 +193,49 @@ export class AlarmService { null, sortOrder); } else if (alarmsQuery.interval) { - pageLink = new TimePageLink(100, 0, + pageLink = new TimePageLink(alarmsQuery.alarmsFetchSize || 100, 0, null, sortOrder, time - alarmsQuery.interval); } else if (alarmsQuery.startTime) { - pageLink = new TimePageLink(100, 0, + pageLink = new TimePageLink(alarmsQuery.alarmsFetchSize || 100, 0, null, sortOrder, Math.round(alarmsQuery.startTime)); if (alarmsQuery.endTime) { pageLink.endTime = Math.round(alarmsQuery.endTime); } } - return this.fetchAlarms(alarmsQuery, pageLink); + let leftToLoad; + if (isDefined(alarmsQuery.alarmsMaxCountLoad) && alarmsQuery.alarmsMaxCountLoad !== 0) { + leftToLoad = alarmsQuery.alarmsMaxCountLoad; + if (leftToLoad < pageLink.pageSize) { + pageLink.pageSize = leftToLoad; + } + } + return this.fetchAlarms(alarmsQuery, pageLink, leftToLoad); } private fetchAlarms(query: AlarmSourceListenerQuery, - pageLink: TimePageLink): Observable> { + pageLink: TimePageLink, leftToLoad?: number): Observable> { const alarmQuery = new AlarmQuery( {id: query.entityId, entityType: query.entityType}, pageLink, query.alarmSearchStatus, query.alarmStatus, - query.fetchOriginator); + query.fetchOriginator, + null); return this.getAlarms(alarmQuery, {ignoreLoading: true}).pipe( expand((data) => { - if (data.hasNext && !query.limit) { - alarmQuery.pageLink.page += 1; + let continueLoad = data.hasNext && !query.limit; + if (continueLoad && isDefined(leftToLoad)) { + leftToLoad -= data.data.length; + if (leftToLoad === 0) { + continueLoad = false; + } else if (leftToLoad < alarmQuery.pageLink.pageSize) { + alarmQuery.pageLink.pageSize = leftToLoad; + } + } + if (continueLoad) { + alarmQuery.offset = data.data[data.data.length-1].id.id; return this.getAlarms(alarmQuery, {ignoreLoading: true}); } else { return EMPTY; diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index a4e1e60e9c..13d5c12af1 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -110,7 +110,7 @@ export class AlarmTableConfig extends EntityTableConfig } fetchAlarms(pageLink: TimePageLink): Observable> { - const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true); + const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true, null); return this.alarmService.getAlarms(query); } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html index 3d05f737a1..aa9bb614bf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html @@ -36,29 +36,60 @@ fxFlex formControlName="timewindow"> -
- - alarm.alarm-status - - - {{ ('alarm.search-status.' + searchStatus) | translate }} - - - - - alarm.polling-interval - - - {{ 'alarm.polling-interval-required' | translate }} - - - {{ 'alarm.min-polling-interval-message' | translate }} - - +
+
+ + alarm.alarm-status + + + {{ ('alarm.search-status.' + searchStatus) | translate }} + + + + + alarm.polling-interval + + + {{ 'alarm.polling-interval-required' | translate }} + + + {{ 'alarm.min-polling-interval-message' | translate }} + + +
+
+ + alarm.max-count-load + + + {{ 'alarm.max-count-load-required' | translate }} + + + {{ 'alarm.max-count-load-error-min' | translate }} + + + + alarm.fetch-size + + + {{ 'alarm.fetch-size-required' | translate }} + + + {{ 'alarm.fetch-size-error-min' | translate }} + + +