Browse Source

Merge branch from develop/3.5.2

pull/8825/head
Vladyslav_Prykhodko 3 years ago
parent
commit
b8954da47b
  1. 10
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  2. 11
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  3. 3
      application/src/main/resources/thingsboard.yml
  4. 28
      application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java
  5. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  6. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  7. 14
      dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
  8. 3
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  9. 26
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmTypesCacheEvictEvent.java
  10. 36
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmTypesCaffeineCache.java
  11. 38
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmTypesRedisCache.java
  12. 34
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  13. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
  14. 8
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  15. 16
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
  16. 17
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
  17. 17
      dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java
  18. 17
      dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java
  19. 3
      dao/src/test/resources/application-test.properties
  20. 9
      rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
  21. 5
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java
  22. 5
      ui-ngx/src/app/core/http/alarm.service.ts
  23. 22
      ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html
  24. 45
      ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss
  25. 43
      ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts
  26. 3
      ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.html
  27. 33
      ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts
  28. 6
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  29. 4
      ui-ngx/src/styles.scss

10
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -29,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
@ -498,4 +499,13 @@ public class AlarmController extends BaseController {
alarmStatus, assigneeId);
}
@ApiOperation(value = "Get Alarm Types (getAlarmTypes)",
notes = "Returns a set of unique alarm types based on alarms that are either owned by the tenant or assigned to the customer which user is performing the request.", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/types", method = RequestMethod.GET)
@ResponseBody
public List<EntitySubtype> getAlarmTypes() throws ThingsboardException, ExecutionException, InterruptedException {
return checkNotNull(alarmService.findAlarmTypesByTenantId(getTenantId()));
}
}

11
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java

@ -25,6 +25,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmComment;
@ -44,20 +45,21 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmTrigger;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmTrigger;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
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.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import java.util.Collection;
import java.util.List;
/**
* Created by ashvayka on 27.03.18.
@ -235,6 +237,11 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
});
}
@Override
public List<EntitySubtype> findAlarmTypesByTenantId(TenantId tenantId) {
return alarmService.findAlarmTypesByTenantId(tenantId);
}
private void onAlarmUpdated(AlarmApiCallResult result) {
wsCallBackExecutor.submit(() -> {
AlarmInfo alarm = result.getAlarm();

3
application/src/main/resources/thingsboard.yml

@ -500,6 +500,9 @@ cache:
resourceInfo:
timeToLiveInMinutes: "${CACHE_SPECS_RESOURCE_INFO_TTL:1440}"
maxSize: "${CACHE_SPECS_RESOURCE_INFO_MAX_SIZE:100000}"
alarmTypes:
timeToLiveInMinutes: "${CACHE_SPECS_ALARM_TYPES_TTL:60}"
maxSize: "${CACHE_SPECS_ALARM_TYPES_MAX_SIZE:10000}"
# deliberately placed outside 'specs' group above
notificationRules:

28
application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java

@ -31,6 +31,7 @@ import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
@ -44,8 +45,12 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -738,4 +743,27 @@ public class AlarmControllerTest extends AbstractControllerTest {
doPost("/api/alarm", alarm).andExpect(status().isForbidden());
}
@Test
public void testGetAlarmTypes() throws Exception {
loginTenantAdmin();
Mockito.reset(tbClusterService, auditLogService);
List<String> types = new ArrayList<>();
for (int i = 1; i < 13; i++) {
types.add(createAlarm(TEST_ALARM_TYPE + i).getType());
}
List<String> foundTypes = doGetTyped("/api/alarm/types", new TypeReference<List<EntitySubtype>>() {})
.stream()
.map(EntitySubtype::getType)
.collect(Collectors.toList());
Collections.sort(types);
Collections.sort(foundTypes);
Assert.assertEquals(types, foundTypes);
}
}

4
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
@ -39,6 +40,7 @@ import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.Collection;
import java.util.List;
public interface AlarmService extends EntityDaoService {
@ -121,4 +123,6 @@ public interface AlarmService extends EntityDaoService {
void deleteEntityAlarmRelations(TenantId tenantId, EntityId entityId);
long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query);
List<EntitySubtype> findAlarmTypesByTenantId(TenantId tenantId);
}

1
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -45,4 +45,5 @@ public class CacheConstants {
public static final String DASHBOARD_TITLES_CACHE = "dashboardTitles";
public static final String ENTITY_COUNT_CACHE = "entityCount";
public static final String RESOURCE_INFO_CACHE = "resourceInfo";
public static final String ALARM_TYPES_CACHE = "alarmTypes";
}

14
dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java

@ -18,6 +18,9 @@ package org.thingsboard.server.dao;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -142,4 +145,15 @@ public abstract class DaoUtil {
return null;
}
}
public static List<EntitySubtype> convertTenantEntityTypesToDto(UUID tenantId, EntityType entityType, List<String> types) {
List<EntitySubtype> list = Collections.emptyList();
if (types != null && !types.isEmpty()) {
list = new ArrayList<>();
for (String type : types) {
list.add(new EntitySubtype(TenantId.fromUUID(tenantId), entityType, type));
}
}
return list;
}
}

3
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
@ -97,4 +98,6 @@ public interface AlarmDao extends Dao<Alarm> {
long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query);
List<EntitySubtype> findTenantAlarmTypesAsync(UUID tenantId);
}

26
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmTypesCacheEvictEvent.java

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.alarm;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.thingsboard.server.common.data.id.TenantId;
@Data
@RequiredArgsConstructor
class AlarmTypesCacheEvictEvent {
private final TenantId tenantId;
}

36
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmTypesCaffeineCache.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.alarm;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.ArrayList;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("AlarmTypesCache")
public class AlarmTypesCaffeineCache extends CaffeineTbTransactionalCache<TenantId, ArrayList<EntitySubtype>> {
public AlarmTypesCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.ALARM_TYPES_CACHE);
}
}

38
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmTypesRedisCache.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.alarm;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbFSTRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.ArrayList;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("AlarmTypesCache")
public class AlarmTypesRedisCache extends RedisTbTransactionalCache<TenantId, ArrayList<EntitySubtype>> {
public AlarmTypesRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.ALARM_TYPES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());
}
}

34
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -22,9 +22,13 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.springframework.util.CollectionUtils;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
@ -54,7 +58,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.ConstraintValidator;
@ -64,6 +68,7 @@ import org.thingsboard.server.dao.tenant.TenantService;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
@ -78,7 +83,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Service("AlarmDaoService")
@Slf4j
@RequiredArgsConstructor
public class BaseAlarmService extends AbstractEntityService implements AlarmService {
public class BaseAlarmService extends AbstractCachedEntityService<TenantId, ArrayList<EntitySubtype>, AlarmTypesCacheEvictEvent> implements AlarmService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
@ -87,6 +92,17 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
private final EntityService entityService;
private final DataValidator<Alarm> alarmDataValidator;
@Autowired
protected TbTransactionalCache<TenantId, ArrayList<EntitySubtype>> alarmTypesCache;
@TransactionalEventListener(classes = AlarmTypesCacheEvictEvent.class)
@Override
public void handleEvictEvent(AlarmTypesCacheEvictEvent event) {
TenantId tenantId = event.getTenantId();
cache.evict(tenantId);
alarmTypesCache.evict(tenantId);
}
@Override
public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) {
validateAlarmRequest(request);
@ -112,6 +128,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
if (!result.isSuccessful() && !alarmCreationEnabled) {
throw new ApiUsageLimitsExceededException("Alarms creation is disabled");
}
publishEvictEvent(new AlarmTypesCacheEvictEvent(request.getTenantId()));
return withPropagated(result);
}
@ -188,6 +205,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} else {
deleteEntityRelations(tenantId, alarm.getId());
alarmDao.removeById(tenantId, alarm.getUuidId());
publishEvictEvent(new AlarmTypesCacheEvictEvent(tenantId));
return AlarmApiCallResult.builder().alarm(alarm).deleted(true).successful(true).build();
}
}
@ -203,12 +221,14 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
AlarmOperationResult result = new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)));
deleteEntityRelations(tenantId, alarm.getId());
alarmDao.removeById(tenantId, alarm.getUuidId());
publishEvictEvent(new AlarmTypesCacheEvictEvent(tenantId));
return result;
}
private AlarmOperationResult createAlarm(Alarm alarm) throws InterruptedException, ExecutionException {
log.debug("New Alarm : {}", alarm);
Alarm saved = alarmDao.save(alarm.getTenantId(), alarm);
publishEvictEvent(new AlarmTypesCacheEvictEvent(alarm.getTenantId()));
List<EntityId> propagatedEntitiesList = createEntityAlarmRecords(saved);
return new AlarmOperationResult(saved, true, true, propagatedEntitiesList);
}
@ -376,6 +396,16 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return alarmDao.countAlarmsByQuery(tenantId, customerId, query);
}
@Override
public List<EntitySubtype> findAlarmTypesByTenantId(TenantId tenantId) {
log.trace("Executing findAlarmTypesByTenantId, tenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
return cache.getAndPutInTransaction(tenantId, () ->
alarmDao.findTenantAlarmTypesAsync(tenantId.getId()).stream()
.sorted(Comparator.comparing(EntitySubtype::getType))
.collect(Collectors.toCollection(ArrayList::new)), false);
}
private Alarm merge(Alarm existing, Alarm alarm) {
if (alarm.getStartTs() > existing.getEndTs()) {
existing.setEndTs(alarm.getStartTs());

3
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java

@ -345,4 +345,7 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
@Query(value = "SELECT unassign_alarm(:t_id, :a_id, :a_ts)", nativeQuery = true)
String unassignAlarm(@Param("t_id") UUID tenantId, @Param("a_id") UUID alarmId, @Param("a_ts") long unassignTime);
@Query(value = "SELECT DISTINCT a.type FROM alarm a WHERE a.tenant_id = :tenantId LIMIT 256", nativeQuery = true)
List<String> findTenantAlarmTypes(@Param("tenantId") UUID tenantId);
}

8
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -24,6 +24,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.alarm.Alarm;
@ -67,6 +68,8 @@ import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityTypesToDto;
/**
* Created by Valerii Sosliuk on 5/19/2017.
*/
@ -366,6 +369,11 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
return alarmQueryRepository.countAlarmsByQuery(tenantId, customerId, query);
}
@Override
public List<EntitySubtype> findTenantAlarmTypesAsync(UUID tenantId) {
return convertTenantEntityTypesToDto(tenantId, EntityType.ALARM, alarmRepository.findTenantAlarmTypes(tenantId));
}
private static String getPropagationTypes(AlarmPropagationInfo ap) {
String propagateRelationTypes;
if (!CollectionUtils.isEmpty(ap.getPropagateRelationTypes())) {

16
dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java

@ -37,14 +37,13 @@ import org.thingsboard.server.dao.model.sql.AssetInfoEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityTypesToDto;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
/**
@ -194,7 +193,7 @@ public class JpaAssetDao extends JpaAbstractDao<AssetEntity, Asset> implements A
@Override
public ListenableFuture<List<EntitySubtype>> findTenantAssetTypesAsync(UUID tenantId) {
return service.submit(() -> convertTenantAssetTypesToDto(tenantId, assetRepository.findTenantAssetTypes(tenantId)));
return service.submit(() -> convertTenantEntityTypesToDto(tenantId, EntityType.ASSET, assetRepository.findTenantAssetTypes(tenantId)));
}
@Override
@ -212,17 +211,6 @@ public class JpaAssetDao extends JpaAbstractDao<AssetEntity, Asset> implements A
DaoUtil.toPageable(pageLink)));
}
private List<EntitySubtype> convertTenantAssetTypesToDto(UUID tenantId, List<String> types) {
List<EntitySubtype> list = Collections.emptyList();
if (types != null && !types.isEmpty()) {
list = new ArrayList<>();
for (String type : types) {
list.add(new EntitySubtype(TenantId.fromUUID(tenantId), EntityType.ASSET, type));
}
}
return list;
}
@Override
public PageData<Asset> findAssetsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, PageLink pageLink) {
log.debug("Try to find assets by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);

17
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java

@ -43,13 +43,13 @@ import org.thingsboard.server.dao.model.sql.DeviceEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityTypesToDto;
/**
* Created by Valerii Sosliuk on 5/6/2017.
*/
@ -217,7 +217,7 @@ public class JpaDeviceDao extends JpaAbstractDao<DeviceEntity, Device> implement
@Override
public ListenableFuture<List<EntitySubtype>> findTenantDeviceTypesAsync(UUID tenantId) {
return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(tenantId)));
return service.submit(() -> convertTenantEntityTypesToDto(tenantId, EntityType.DEVICE, deviceRepository.findTenantDeviceTypes(tenantId)));
}
@Override
@ -240,17 +240,6 @@ public class JpaDeviceDao extends JpaAbstractDao<DeviceEntity, Device> implement
return deviceRepository.countByTenantId(tenantId.getId());
}
private List<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) {
List<EntitySubtype> list = Collections.emptyList();
if (types != null && !types.isEmpty()) {
list = new ArrayList<>();
for (String type : types) {
list.add(new EntitySubtype(TenantId.fromUUID(tenantId), EntityType.DEVICE, type));
}
}
return list;
}
@Override
public PageData<Device> findDevicesByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, PageLink pageLink) {
log.debug("Try to find devices by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);

17
dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java

@ -34,13 +34,13 @@ import org.thingsboard.server.dao.model.sql.EdgeInfoEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityTypesToDto;
@Component
@Slf4j
@SqlDao
@ -144,7 +144,7 @@ public class JpaEdgeDao extends JpaAbstractDao<EdgeEntity, Edge> implements Edge
@Override
public ListenableFuture<List<EntitySubtype>> findTenantEdgeTypesAsync(UUID tenantId) {
return service.submit(() -> convertTenantEdgeTypesToDto(tenantId, edgeRepository.findTenantEdgeTypes(tenantId)));
return service.submit(() -> convertTenantEntityTypesToDto(tenantId, EntityType.EDGE, edgeRepository.findTenantEdgeTypes(tenantId)));
}
@Override
@ -184,17 +184,6 @@ public class JpaEdgeDao extends JpaAbstractDao<EdgeEntity, Edge> implements Edge
DaoUtil.toPageable(pageLink)));
}
private List<EntitySubtype> convertTenantEdgeTypesToDto(UUID tenantId, List<String> types) {
List<EntitySubtype> list = Collections.emptyList();
if (types != null && !types.isEmpty()) {
list = new ArrayList<>();
for (String type : types) {
list.add(new EntitySubtype(TenantId.fromUUID(tenantId), EntityType.EDGE, type));
}
}
return list;
}
@Override
public EntityType getEntityType() {
return EntityType.EDGE;

17
dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java

@ -35,13 +35,13 @@ import org.thingsboard.server.dao.model.sql.EntityViewInfoEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityTypesToDto;
/**
* Created by Victor Basanets on 8/31/2017.
*/
@ -167,18 +167,7 @@ public class JpaEntityViewDao extends JpaAbstractDao<EntityViewEntity, EntityVie
@Override
public ListenableFuture<List<EntitySubtype>> findTenantEntityViewTypesAsync(UUID tenantId) {
return service.submit(() -> convertTenantEntityViewTypesToDto(tenantId, entityViewRepository.findTenantEntityViewTypes(tenantId)));
}
private List<EntitySubtype> convertTenantEntityViewTypesToDto(UUID tenantId, List<String> types) {
List<EntitySubtype> list = Collections.emptyList();
if (types != null && !types.isEmpty()) {
list = new ArrayList<>();
for (String type : types) {
list.add(new EntitySubtype(TenantId.fromUUID(tenantId), EntityType.ENTITY_VIEW, type));
}
}
return list;
return service.submit(() -> convertTenantEntityTypesToDto(tenantId, EntityType.ENTITY_VIEW, entityViewRepository.findTenantEntityViewTypes(tenantId)));
}
@Override

3
dao/src/test/resources/application-test.properties

@ -77,6 +77,9 @@ cache.specs.entityCount.maxSize=10000
cache.specs.resourceInfo.timeToLiveInMinutes=1440
cache.specs.resourceInfo.maxSize=10000
cache.specs.alarmTypes.timeToLiveInMinutes=60
cache.specs.alarmTypes.maxSize=10000
redis.connection.host=localhost
redis.connection.port=6379
redis.connection.db=0

9
rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java

@ -499,6 +499,15 @@ public class RestClient implements Closeable {
return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody();
}
public List<EntitySubtype> getAlarmTypes() {
return restTemplate.exchange(
baseURL + "/api/alarm/types",
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<List<EntitySubtype>>() {
}).getBody();
}
public AlarmComment saveAlarmComment(AlarmId alarmId, AlarmComment alarmComment) {
return restTemplate.postForEntity(baseURL + "/api/alarm/{alarmId}/comment", alarmComment, AlarmComment.class, alarmId.getId()).getBody();
}

5
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java

@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.api;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
@ -39,6 +40,7 @@ import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import java.util.Collection;
import java.util.List;
/**
* Created by ashvayka on 02.04.18.
@ -54,6 +56,7 @@ public interface RuleEngineAlarmService {
* Only one active alarm may exist for the pair {originatorId, alarmType}
*/
AlarmApiCallResult createAlarm(AlarmCreateOrUpdateActiveRequest request);
/**
* Designed to update existing alarm. Accepts only part of the alarm fields.
*/
@ -110,4 +113,6 @@ public interface RuleEngineAlarmService {
AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, String assigneeId);
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
List<EntitySubtype> findAlarmTypesByTenantId(TenantId tenantId);
}

5
ui-ngx/src/app/core/http/alarm.service.ts

@ -29,6 +29,7 @@ import {
AlarmStatus
} from '@shared/models/alarm.models';
import { UtilsService } from '@core/services/utils.service';
import { EntitySubtype } from '@shared/models/entity-type.models';
@Injectable({
providedIn: 'root'
@ -108,4 +109,8 @@ export class AlarmService {
defaultHttpOptionsFromConfig(config));
}
public getAlarmTypes(config?: RequestConfig): Observable<Array<EntitySubtype>> {
return this.http.get<Array<EntitySubtype>>('/api/alarm/types', defaultHttpOptionsFromConfig(config));
}
}

22
ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html

@ -55,7 +55,7 @@
</form>
</ng-template>
<ng-template #alarmFilter>
<div class="tb-form-panel no-padding no-border" [formGroup]="alarmFilterConfigForm">
<div class="tb-form-panel no-padding no-border tb-alarm-filter-config-component" [formGroup]="alarmFilterConfigForm">
<div class="tb-form-row space-between" ngClass.xs="filters-row-mobile">
<div class="fixed-title-width" ngClass.xs="filters-title-mobile" translate>alarm.alarm-status-list</div>
<mat-chip-listbox multiple formControlName="statusList">
@ -74,23 +74,9 @@
</div>
<div class="tb-form-row" ngClass.xs="filters-row-mobile">
<div class="fixed-title-width" ngClass.xs="filters-title-mobile" translate>alarm.alarm-type-list</div>
<mat-form-field fxFlex appearance="outline"
subscriptSizing="dynamic"
class="tb-chips"
ngClass.xs="filters-fields-width-mobile">
<mat-chip-grid #alarmTypeChipList formControlName="typeList">
<mat-chip-row *ngFor="let type of alarmTypeList()"
[removable]="true" (removed)="removeAlarmType(type)">
{{type}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip-row>
<input placeholder="{{ !alarmFilterConfigForm.get('typeList').value?.length ? ('alarm.any-type' | translate) : '' }}"
[matChipInputFor]="alarmTypeChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
matChipInputAddOnBlur
(matChipInputTokenEnd)="addAlarmType($event)">
</mat-chip-grid>
</mat-form-field>
<tb-entity-subtype-list subscriptSizing="dynamic" appearance="outline" addClass="tb-chips" ngClass.xs="filters-fields-width-mobile"
[entityType]="entityType.ALARM" formControlName="typeList">
</tb-entity-subtype-list>
</div>
<div class="tb-form-row" ngClass.xs="filters-row-mobile">
<div class="fixed-title-width" ngClass.xs="filters-title-mobile" translate>alarm.assignee</div>

45
ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.scss

@ -15,24 +15,10 @@
*/
:host {
display: block;
overflow: scroll;
max-width: 100%;
.mdc-button {
max-width: 100%;
}
.filters-row-mobile {
flex-direction: column;
align-items: start;
border: none;
padding: 0;
}
.filters-title-mobile {
font-size: 14px;
}
.filters-fields-width-mobile {
width: 100%;
}
}
:host ::ng-deep {
@ -45,9 +31,34 @@
text-overflow: ellipsis;
}
}
.mat-mdc-chip {
.mdc-evolution-chip__cell, .mat-mdc-chip-action, .mat-mdc-chip-action-label {
overflow: hidden;
}
::ng-deep {
.tb-alarm-filter-config-component{
.filters-row-mobile {
flex-direction: column;
align-items: start;
border: none;
padding: 0;
}
.filters-title-mobile {
font-size: 14px;
}
.filters-fields-width-mobile {
width: 100%;
}
.mat-mdc-chip {
.mdc-evolution-chip__cell, .mat-mdc-chip-action, .mat-mdc-chip-action-label {
overflow: hidden;
}
}
tb-entity-subtype-list {
flex: 1 1 0;
display: flex;
& > * {
flex: 1 1 0;
}
}
}
}

43
ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts

@ -40,10 +40,9 @@ import {
AlarmSeverity,
alarmSeverityTranslations
} from '@shared/models/alarm.models';
import { MatChipInputEvent } from '@angular/material/chips';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { TranslateService } from '@ngx-translate/core';
import { deepClone } from '@core/utils';
import { EntityType } from '@shared/models/entity-type.models';
export const ALARM_FILTER_CONFIG_DATA = new InjectionToken<any>('AlarmFilterConfigData');
@ -91,8 +90,6 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
panelMode = false;
readonly separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON];
alarmSearchStatuses = [AlarmSearchStatus.ACTIVE,
AlarmSearchStatus.CLEARED,
AlarmSearchStatus.ACK,
@ -113,6 +110,8 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
panelResult: AlarmFilterConfig = null;
entityType = EntityType;
private alarmFilterConfig: AlarmFilterConfig;
private propagateChange = (_: any) => {};
@ -190,7 +189,7 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
$event.stopPropagation();
}
const config = new OverlayConfig({
panelClass: 'tb-filter-panel',
panelClass: ['tb-filter-panel', 'tb-scroll'],
backdropClass: 'cdk-overlay-transparent-backdrop',
hasBackdrop: true,
maxHeight: '80vh',
@ -256,40 +255,6 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
}
}
public alarmTypeList(): string[] {
return this.alarmFilterConfigForm.get('typeList').value;
}
public removeAlarmType(type: string): void {
const types: string[] = this.alarmFilterConfigForm.get('typeList').value;
const index = types.indexOf(type);
if (index >= 0) {
types.splice(index, 1);
this.alarmFilterConfigForm.get('typeList').setValue(types);
this.alarmFilterConfigForm.get('typeList').markAsDirty();
}
}
public addAlarmType(event: MatChipInputEvent): void {
const input = event.chipInput.inputElement;
const value = event.value;
let types: string[] = this.alarmFilterConfigForm.get('typeList').value;
if ((value || '').trim()) {
if (!types) {
types = [];
}
types.push(value.trim());
this.alarmFilterConfigForm.get('typeList').setValue(types);
this.alarmFilterConfigForm.get('typeList').markAsDirty();
}
if (input) {
input.value = '';
}
}
private updateAlarmConfigForm(alarmFilterConfig?: AlarmFilterConfig) {
this.alarmFilterConfigForm.patchValue({
statusList: alarmFilterConfig?.statusList,

3
ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.html

@ -15,7 +15,8 @@
limitations under the License.
-->
<mat-form-field [formGroup]="entitySubtypeListFormGroup" [ngClass]="label ? '' : 'tb-chip-list'" class="mat-block">
<mat-form-field [formGroup]="entitySubtypeListFormGroup" [ngClass]="label ? addClass : 'tb-chip-list ' + addClass" class="mat-block"
appearance="{{ appearance }}" subscriptSizing="{{ subscriptSizing }}">
<mat-label *ngIf="label">{{ label }}</mat-label>
<mat-chip-grid #chipList formControlName="entitySubtypeList">
<mat-chip-row

33
ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts

@ -24,13 +24,15 @@ import { TranslateService } from '@ngx-translate/core';
import { EntitySubtype, EntityType } from '@shared/models/entity-type.models';
import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatChipInputEvent, MatChipGrid } from '@angular/material/chips';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { AssetService } from '@core/http/asset.service';
import { DeviceService } from '@core/http/device.service';
import { EdgeService } from '@core/http/edge.service';
import { EntityViewService } from '@core/http/entity-view.service';
import { BroadcastService } from '@core/services/broadcast.service';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { AlarmService } from '@core/http/alarm.service';
import { coerceBoolean } from '@shared/decorators/coercion';
import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
@Component({
selector: 'tb-entity-subtype-list',
@ -58,10 +60,10 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit,
@Input() label: string;
@Input()
@coerceBoolean()
set required(value: boolean) {
const newVal = coerceBooleanProperty(value);
if (this.requiredValue !== newVal) {
this.requiredValue = newVal;
if (this.requiredValue !== value) {
this.requiredValue = value;
this.updateValidators();
}
}
@ -72,6 +74,15 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit,
@Input()
entityType: EntityType;
@Input()
appearance: MatFormFieldAppearance = 'fill';
@Input()
subscriptSizing: SubscriptSizing = 'fixed';
@Input()
addClass: string = '';
@ViewChild('entitySubtypeInput') entitySubtypeInput: ElementRef<HTMLInputElement>;
@ViewChild('entitySubtypeAutocomplete') entitySubtypeAutocomplete: MatAutocomplete;
@ViewChild('chipList', {static: true}) chipList: MatChipGrid;
@ -102,6 +113,7 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit,
private deviceService: DeviceService,
private edgeService: EdgeService,
private entityViewService: EntityViewService,
private alarmService: AlarmService,
private fb: UntypedFormBuilder) {
this.entitySubtypeListFormGroup = this.fb.group({
entitySubtypeList: [this.entitySubtypeList, this.required ? [Validators.required] : []],
@ -164,6 +176,16 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit,
this.entitySubtypes = null;
});
break;
case EntityType.ALARM:
this.placeholder = this.required ? this.translate.instant('alarm.enter-alarm-type')
: this.translate.instant('alarm.any-type');
this.secondaryPlaceholder = '+' + this.translate.instant('alarm.alarm-type');
this.noSubtypesMathingText = 'alarm.no-alarm-types-matching';
this.subtypeListEmptyText = 'alarm.alarm-type-list-empty';
this.broadcastSubscription = this.broadcast.on('alarmSaved', () => {
this.entitySubtypes = null;
});
break;
}
this.filteredEntitySubtypeList = this.entitySubtypeListFormGroup.get('entitySubtype').valueChanges
@ -283,6 +305,9 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit,
case EntityType.ENTITY_VIEW:
subTypesObservable = this.entityViewService.getEntityViewTypes({ignoreLoading: true});
break;
case EntityType.ALARM:
subTypesObservable = this.alarmService.getAlarmTypes({ignoreLoading: true});
break;
}
if (subTypesObservable) {
this.entitySubtypes = subTypesObservable.pipe(

6
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -537,7 +537,11 @@
"search-propagated-alarms": "Search propagated alarms",
"comments": "Alarm comments",
"show-more": "Show more",
"additional-info": "Additional info"
"additional-info": "Additional info",
"alarm-type": "Alarm type",
"enter-alarm-type": "Enter alarm type",
"no-alarm-types-matching": "No alarm types matching '{{entitySubtype}}' were found.",
"alarm-type-list-empty": "No alarm types selected."
},
"alarm-activity": {
"add": "Add a comment...",

4
ui-ngx/src/styles.scss

@ -276,6 +276,10 @@ pre.tb-highlight {
0 7px 8px -4px rgba(0, 0, 0, .2),
0 13px 19px 2px rgba(0, 0, 0, .14),
0 5px 24px 4px rgba(0, 0, 0, .12);
&.tb-scroll {
overflow: scroll;
}
}
.tb-panel-actions {

Loading…
Cancel
Save