Commit 620b66b7008abfacb9f15b7bb4a699e4834c864e

Authored by Andrii Shvaika
2 parents c4fd267f 416432e3

Merge branch 'develop/3.0' of github.com:thingsboard/thingsboard into develop/3.0

... ... @@ -44,6 +44,8 @@ import org.thingsboard.server.common.data.page.TimePageLink;
44 44 import org.thingsboard.server.service.security.permission.Operation;
45 45 import org.thingsboard.server.service.security.permission.Resource;
46 46
  47 +import java.util.UUID;
  48 +
47 49 @RestController
48 50 @RequestMapping("/api")
49 51 public class AlarmController extends BaseController {
... ... @@ -155,6 +157,7 @@ public class AlarmController extends BaseController {
155 157 @RequestParam(required = false) String sortOrder,
156 158 @RequestParam(required = false) Long startTime,
157 159 @RequestParam(required = false) Long endTime,
  160 + @RequestParam(required = false) String offset,
158 161 @RequestParam(required = false) Boolean fetchOriginator
159 162 ) throws ThingsboardException {
160 163 checkParameter("EntityId", strEntityId);
... ... @@ -168,8 +171,12 @@ public class AlarmController extends BaseController {
168 171 }
169 172 checkEntityId(entityId, Operation.READ);
170 173 TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
  174 + UUID idOffsetUuid = null;
  175 + if (StringUtils.isNotEmpty(offset)) {
  176 + idOffsetUuid = toUUID(offset);
  177 + }
171 178 try {
172   - return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
  179 + return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator, idOffsetUuid)).get());
173 180 } catch (Exception e) {
174 181 throw handleException(e);
175 182 }
... ...
... ... @@ -22,6 +22,8 @@ import org.thingsboard.server.common.data.id.EntityId;
22 22 import org.thingsboard.server.common.data.id.TenantId;
23 23 import org.thingsboard.server.common.data.page.TimePageLink;
24 24
  25 +import java.util.UUID;
  26 +
25 27 /**
26 28 * Created by ashvayka on 11.05.17.
27 29 */
... ... @@ -35,5 +37,6 @@ public class AlarmQuery {
35 37 private AlarmSearchStatus searchStatus;
36 38 private AlarmStatus status;
37 39 private Boolean fetchOriginator;
  40 + private UUID idOffset;
38 41
39 42 }
... ...
... ... @@ -300,7 +300,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
300 300 AlarmSeverity highestSeverity = null;
301 301 AlarmQuery query;
302 302 while (hasNext && AlarmSeverity.CRITICAL != highestSeverity) {
303   - query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false);
  303 + query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false, null);
304 304 PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, query);
305 305 if (alarms.hasNext()) {
306 306 nextPageLink = nextPageLink.nextPageLink();
... ...
... ... @@ -33,7 +33,7 @@ import java.util.List;
33 33 @SqlDao
34 34 public interface AlarmRepository extends CrudRepository<AlarmEntity, String> {
35 35
36   - @Query("SELECT a FROM AlarmEntity a WHERE a.originatorId = :originatorId AND a.type = :alarmType ORDER BY startTs DESC")
  36 + @Query("SELECT a FROM AlarmEntity a WHERE a.originatorId = :originatorId AND a.type = :alarmType ORDER BY a.startTs DESC")
37 37 List<AlarmEntity> findLatestByOriginatorAndType(@Param("originatorId") String originatorId,
38 38 @Param("alarmType") String alarmType,
39 39 Pageable pageable);
... ... @@ -48,6 +48,7 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, String> {
48 48 "AND re.fromType = :affectedEntityType " +
49 49 "AND (:startId IS NULL OR a.id >= :startId) " +
50 50 "AND (:endId IS NULL OR a.id <= :endId) " +
  51 + "AND (:idOffset IS NULL OR a.id < :idOffset) " +
51 52 "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%'))" +
52 53 "OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%'))" +
53 54 "OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%')))")
... ... @@ -57,6 +58,7 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, String> {
57 58 @Param("relationType") String relationType,
58 59 @Param("startId") String startId,
59 60 @Param("endId") String endId,
  61 + @Param("idOffset") String idOffset,
60 62 @Param("searchText") String searchText,
61 63 Pageable pageable);
62 64 }
... ...
... ... @@ -117,6 +117,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
117 117 relationType,
118 118 startTimeToId(query.getPageLink().getStartTime()),
119 119 endTimeToId(query.getPageLink().getEndTime()),
  120 + query.getIdOffset() != null ? UUIDConverter.fromTimeUUID(query.getIdOffset()) : null,
120 121 Objects.toString(query.getPageLink().getTextSearch(), ""),
121 122 DaoUtil.toPageable(query.getPageLink())
122 123 )
... ...
... ... @@ -190,6 +190,8 @@ export interface WidgetSubscriptionOptions {
190 190 alarmSource?: Datasource;
191 191 alarmSearchStatus?: AlarmSearchStatus;
192 192 alarmsPollingInterval?: number;
  193 + alarmsMaxCountLoad?: number;
  194 + alarmsFetchSize?: number;
193 195 datasources?: Array<Datasource>;
194 196 targetDeviceAliasIds?: Array<string>;
195 197 targetDeviceIds?: Array<string>;
... ...
... ... @@ -93,6 +93,8 @@ export class WidgetSubscription implements IWidgetSubscription {
93 93 }
94 94
95 95 alarmsPollingInterval: number;
  96 + alarmsMaxCountLoad: number;
  97 + alarmsFetchSize: number;
96 98 alarmSourceListener: AlarmSourceListener;
97 99
98 100 loadingData: boolean;
... ... @@ -153,6 +155,10 @@ export class WidgetSubscription implements IWidgetSubscription {
153 155 options.alarmSearchStatus : AlarmSearchStatus.ANY;
154 156 this.alarmsPollingInterval = isDefined(options.alarmsPollingInterval) ?
155 157 options.alarmsPollingInterval : 5000;
  158 + this.alarmsMaxCountLoad = isDefined(options.alarmsMaxCountLoad) ?
  159 + options.alarmsMaxCountLoad : 0;
  160 + this.alarmsFetchSize = isDefined(options.alarmsFetchSize) ?
  161 + options.alarmsFetchSize : 100;
156 162 this.alarmSourceListener = null;
157 163 this.alarms = [];
158 164 this.originalTimewindow = null;
... ... @@ -712,6 +718,8 @@ export class WidgetSubscription implements IWidgetSubscription {
712 718 alarmSource: this.alarmSource,
713 719 alarmSearchStatus: this.alarmSearchStatus,
714 720 alarmsPollingInterval: this.alarmsPollingInterval,
  721 + alarmsMaxCountLoad: this.alarmsMaxCountLoad,
  722 + alarmsFetchSize: this.alarmsFetchSize,
715 723 alarmsUpdated: alarms => this.alarmsUpdated(alarms)
716 724 };
717 725 this.alarms = null;
... ...
... ... @@ -38,12 +38,15 @@ import { Direction, SortOrder } from '@shared/models/page/sort-order';
38 38 import { concatMap, expand, map, toArray } from 'rxjs/operators';
39 39 import { EMPTY } from 'rxjs';
40 40 import Timeout = NodeJS.Timeout;
  41 +import { isDefined } from '@core/utils';
41 42
42 43 interface AlarmSourceListenerQuery {
43 44 entityType: EntityType;
44 45 entityId: string;
45 46 alarmSearchStatus: AlarmSearchStatus;
46 47 alarmStatus: AlarmStatus;
  48 + alarmsMaxCountLoad: number;
  49 + alarmsFetchSize: number;
47 50 fetchOriginator?: boolean;
48 51 limit?: number;
49 52 interval?: number;
... ... @@ -58,6 +61,8 @@ export interface AlarmSourceListener {
58 61 alarmSource: Datasource;
59 62 alarmsPollingInterval: number;
60 63 alarmSearchStatus: AlarmSearchStatus;
  64 + alarmsMaxCountLoad: number;
  65 + alarmsFetchSize: number;
61 66 alarmsUpdated: (alarms: Array<AlarmInfo>) => void;
62 67 lastUpdateTs?: number;
63 68 alarmsQuery?: AlarmSourceListenerQuery;
... ... @@ -132,7 +137,9 @@ export class AlarmService {
132 137 entityType: alarmSource.entityType,
133 138 entityId: alarmSource.entityId,
134 139 alarmSearchStatus: alarmSourceListener.alarmSearchStatus,
135   - alarmStatus: null
  140 + alarmStatus: null,
  141 + alarmsMaxCountLoad: alarmSourceListener.alarmsMaxCountLoad,
  142 + alarmsFetchSize: alarmSourceListener.alarmsFetchSize
136 143 };
137 144 const originatorKeys = alarmSource.dataKeys.filter(dataKey => dataKey.name.toLocaleLowerCase().includes('originator'));
138 145 if (originatorKeys.length) {
... ... @@ -186,32 +193,49 @@ export class AlarmService {
186 193 null,
187 194 sortOrder);
188 195 } else if (alarmsQuery.interval) {
189   - pageLink = new TimePageLink(100, 0,
  196 + pageLink = new TimePageLink(alarmsQuery.alarmsFetchSize || 100, 0,
190 197 null,
191 198 sortOrder, time - alarmsQuery.interval);
192 199 } else if (alarmsQuery.startTime) {
193   - pageLink = new TimePageLink(100, 0,
  200 + pageLink = new TimePageLink(alarmsQuery.alarmsFetchSize || 100, 0,
194 201 null,
195 202 sortOrder, Math.round(alarmsQuery.startTime));
196 203 if (alarmsQuery.endTime) {
197 204 pageLink.endTime = Math.round(alarmsQuery.endTime);
198 205 }
199 206 }
200   - return this.fetchAlarms(alarmsQuery, pageLink);
  207 + let leftToLoad;
  208 + if (isDefined(alarmsQuery.alarmsMaxCountLoad) && alarmsQuery.alarmsMaxCountLoad !== 0) {
  209 + leftToLoad = alarmsQuery.alarmsMaxCountLoad;
  210 + if (leftToLoad < pageLink.pageSize) {
  211 + pageLink.pageSize = leftToLoad;
  212 + }
  213 + }
  214 + return this.fetchAlarms(alarmsQuery, pageLink, leftToLoad);
201 215 }
202 216
203 217 private fetchAlarms(query: AlarmSourceListenerQuery,
204   - pageLink: TimePageLink): Observable<Array<AlarmInfo>> {
  218 + pageLink: TimePageLink, leftToLoad?: number): Observable<Array<AlarmInfo>> {
205 219 const alarmQuery = new AlarmQuery(
206 220 {id: query.entityId, entityType: query.entityType},
207 221 pageLink,
208 222 query.alarmSearchStatus,
209 223 query.alarmStatus,
210   - query.fetchOriginator);
  224 + query.fetchOriginator,
  225 + null);
211 226 return this.getAlarms(alarmQuery, {ignoreLoading: true}).pipe(
212 227 expand((data) => {
213   - if (data.hasNext && !query.limit) {
214   - alarmQuery.pageLink.page += 1;
  228 + let continueLoad = data.hasNext && !query.limit;
  229 + if (continueLoad && isDefined(leftToLoad)) {
  230 + leftToLoad -= data.data.length;
  231 + if (leftToLoad === 0) {
  232 + continueLoad = false;
  233 + } else if (leftToLoad < alarmQuery.pageLink.pageSize) {
  234 + alarmQuery.pageLink.pageSize = leftToLoad;
  235 + }
  236 + }
  237 + if (continueLoad) {
  238 + alarmQuery.offset = data.data[data.data.length-1].id.id;
215 239 return this.getAlarms(alarmQuery, {ignoreLoading: true});
216 240 } else {
217 241 return EMPTY;
... ...
... ... @@ -110,7 +110,7 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
110 110 }
111 111
112 112 fetchAlarms(pageLink: TimePageLink): Observable<PageData<AlarmInfo>> {
113   - const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true);
  113 + const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true, null);
114 114 return this.alarmService.getAlarms(query);
115 115 }
116 116
... ...
... ... @@ -36,29 +36,60 @@
36 36 fxFlex formControlName="timewindow"></tb-timewindow>
37 37 </section>
38 38 </div>
39   - <div *ngIf="widgetType === widgetTypes.alarm" fxLayout="column" fxLayoutGap="8px" fxLayoutAlign="center"
40   - fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="start center">
41   - <mat-form-field fxFlex class="mat-block">
42   - <mat-label translate>alarm.alarm-status</mat-label>
43   - <mat-select matInput formControlName="alarmSearchStatus">
44   - <mat-option *ngFor="let searchStatus of alarmSearchStatuses" [value]="searchStatus">
45   - {{ ('alarm.search-status.' + searchStatus) | translate }}
46   - </mat-option>
47   - </mat-select>
48   - </mat-form-field>
49   - <mat-form-field fxFlex class="mat-block">
50   - <mat-label translate>alarm.polling-interval</mat-label>
51   - <input matInput required
52   - formControlName="alarmsPollingInterval"
53   - type="number"
54   - step="1"/>
55   - <mat-error *ngIf="dataSettings.get('alarmsPollingInterval').hasError('required')">
56   - {{ 'alarm.polling-interval-required' | translate }}
57   - </mat-error>
58   - <mat-error *ngIf="dataSettings.get('alarmsPollingInterval').hasError('min')">
59   - {{ 'alarm.min-polling-interval-message' | translate }}
60   - </mat-error>
61   - </mat-form-field>
  39 + <div *ngIf="widgetType === widgetTypes.alarm" fxLayout="column" fxLayoutAlign="center">
  40 + <div fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="start center" fxLayoutGap="8px">
  41 + <mat-form-field fxFlex class="mat-block">
  42 + <mat-label translate>alarm.alarm-status</mat-label>
  43 + <mat-select matInput formControlName="alarmSearchStatus">
  44 + <mat-option *ngFor="let searchStatus of alarmSearchStatuses" [value]="searchStatus">
  45 + {{ ('alarm.search-status.' + searchStatus) | translate }}
  46 + </mat-option>
  47 + </mat-select>
  48 + </mat-form-field>
  49 + <mat-form-field fxFlex class="mat-block">
  50 + <mat-label translate>alarm.polling-interval</mat-label>
  51 + <input matInput required
  52 + formControlName="alarmsPollingInterval"
  53 + type="number"
  54 + step="1"/>
  55 + <mat-error *ngIf="dataSettings.get('alarmsPollingInterval').hasError('required')">
  56 + {{ 'alarm.polling-interval-required' | translate }}
  57 + </mat-error>
  58 + <mat-error *ngIf="dataSettings.get('alarmsPollingInterval').hasError('min')">
  59 + {{ 'alarm.min-polling-interval-message' | translate }}
  60 + </mat-error>
  61 + </mat-form-field>
  62 + </div>
  63 + <div fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="start center" fxLayoutGap="8px">
  64 + <mat-form-field fxFlex class="mat-block">
  65 + <mat-label translate>alarm.max-count-load</mat-label>
  66 + <input matInput required
  67 + formControlName="alarmsMaxCountLoad"
  68 + type="number"
  69 + min="0"
  70 + step="1">
  71 + <mat-error *ngIf="dataSettings.get('alarmsMaxCountLoad').hasError('required')">
  72 + {{ 'alarm.max-count-load-required' | translate }}
  73 + </mat-error>
  74 + <mat-error *ngIf="dataSettings.get('alarmsMaxCountLoad').hasError('min')">
  75 + {{ 'alarm.max-count-load-error-min' | translate }}
  76 + </mat-error>
  77 + </mat-form-field>
  78 + <mat-form-field fxFlex class="mat-block">
  79 + <mat-label translate>alarm.fetch-size</mat-label>
  80 + <input matInput required
  81 + formControlName="alarmsFetchSize"
  82 + type="number"
  83 + min="10"
  84 + step="1">
  85 + <mat-error *ngIf="dataSettings.get('alarmsFetchSize').hasError('required')">
  86 + {{ 'alarm.fetch-size-required' | translate }}
  87 + </mat-error>
  88 + <mat-error *ngIf="dataSettings.get('alarmsFetchSize').hasError('min')">
  89 + {{ 'alarm.fetch-size-error-min' | translate }}
  90 + </mat-error>
  91 + </mat-form-field>
  92 + </div>
62 93 </div>
63 94 <mat-expansion-panel class="tb-datasources" *ngIf="widgetType !== widgetTypes.rpc &&
64 95 widgetType !== widgetTypes.alarm &&
... ...
... ... @@ -287,6 +287,10 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
287 287 this.dataSettings.addControl('alarmSearchStatus', this.fb.control(null));
288 288 this.dataSettings.addControl('alarmsPollingInterval', this.fb.control(null,
289 289 [Validators.required, Validators.min(1)]));
  290 + this.dataSettings.addControl('alarmsMaxCountLoad', this.fb.control(null,
  291 + [Validators.required, Validators.min(0)]));
  292 + this.dataSettings.addControl('alarmsFetchSize', this.fb.control(null,
  293 + [Validators.required, Validators.min(10)]));
290 294 }
291 295 }
292 296 if (this.modelValue.isDataEnabled) {
... ... @@ -439,6 +443,14 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
439 443 { alarmsPollingInterval: isDefined(config.alarmsPollingInterval) ?
440 444 config.alarmsPollingInterval : 5}, {emitEvent: false}
441 445 );
  446 + this.dataSettings.patchValue(
  447 + { alarmsMaxCountLoad: isDefined(config.alarmsMaxCountLoad) ?
  448 + config.alarmsMaxCountLoad : 0}, {emitEvent: false}
  449 + );
  450 + this.dataSettings.patchValue(
  451 + { alarmsFetchSize: isDefined(config.alarmsFetchSize) ?
  452 + config.alarmsFetchSize : 100}, {emitEvent: false}
  453 + );
442 454 this.alarmSourceSettings.patchValue(
443 455 config.alarmSource, {emitEvent: false}
444 456 );
... ...
... ... @@ -828,6 +828,10 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
828 828 this.widget.config.alarmSearchStatus : AlarmSearchStatus.ANY;
829 829 options.alarmsPollingInterval = isDefined(this.widget.config.alarmsPollingInterval) ?
830 830 this.widget.config.alarmsPollingInterval * 1000 : 5000;
  831 + options.alarmsMaxCountLoad = isDefined(this.widget.config.alarmsMaxCountLoad) ?
  832 + this.widget.config.alarmsMaxCountLoad : 0;
  833 + options.alarmsFetchSize = isDefined(this.widget.config.alarmsFetchSize) ?
  834 + this.widget.config.alarmsFetchSize : 100;
831 835 } else {
832 836 options.datasources = deepClone(this.widget.config.datasources);
833 837 }
... ...
... ... @@ -14,16 +14,14 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import {BaseData} from '@shared/models/base-data';
18   -import {AssetId} from '@shared/models/id/asset-id';
19   -import {TenantId} from '@shared/models/id/tenant-id';
20   -import {CustomerId} from '@shared/models/id/customer-id';
21   -import {AlarmId} from '@shared/models/id/alarm-id';
22   -import {EntityId} from '@shared/models/id/entity-id';
23   -import { ActionStatus } from '@shared/models/audit-log.models';
  17 +import { BaseData } from '@shared/models/base-data';
  18 +import { TenantId } from '@shared/models/id/tenant-id';
  19 +import { AlarmId } from '@shared/models/id/alarm-id';
  20 +import { EntityId } from '@shared/models/id/entity-id';
24 21 import { TimePageLink } from '@shared/models/page/page-link';
25 22 import { NULL_UUID } from '@shared/models/id/has-uuid';
26 23 import { EntityType } from '@shared/models/entity-type.models';
  24 +import { isString } from '@core/utils';
27 25
28 26 export enum AlarmSeverity {
29 27 CRITICAL = 'CRITICAL',
... ... @@ -199,15 +197,17 @@ export class AlarmQuery {
199 197 searchStatus: AlarmSearchStatus;
200 198 status: AlarmStatus;
201 199 fetchOriginator: boolean;
  200 + offset: string;
202 201
203 202 constructor(entityId: EntityId, pageLink: TimePageLink,
204 203 searchStatus: AlarmSearchStatus, status: AlarmStatus,
205   - fetchOriginator: boolean) {
  204 + fetchOriginator: boolean, offset: string) {
206 205 this.affectedEntityId = entityId;
207 206 this.pageLink = pageLink;
208 207 this.searchStatus = searchStatus;
209 208 this.status = status;
210 209 this.fetchOriginator = fetchOriginator;
  210 + this.offset = offset;
211 211 }
212 212
213 213 public toQuery(): string {
... ... @@ -221,6 +221,9 @@ export class AlarmQuery {
221 221 if (typeof this.fetchOriginator !== 'undefined' && this.fetchOriginator !== null) {
222 222 query += `&fetchOriginator=${this.fetchOriginator}`;
223 223 }
  224 + if (isString(this.offset) && this.offset.length) {
  225 + query += `&offset=${this.offset}`;
  226 + }
224 227 return query;
225 228 }
226 229
... ...
... ... @@ -360,6 +360,8 @@ export interface WidgetConfig {
360 360 alarmSource?: Datasource;
361 361 alarmSearchStatus?: AlarmSearchStatus;
362 362 alarmsPollingInterval?: number;
  363 + alarmsMaxCountLoad?: number;
  364 + alarmsFetchSize?: number;
363 365 datasources?: Array<Datasource>;
364 366 targetDeviceAliasIds?: Array<string>;
365 367 [key: string]: any;
... ...