Merge branch 'feature/alarm-types' of github.com:YevhenBondarenko/thingsboard into YevhenBondarenko-feature/alarm-types
This commit is contained in:
commit
35284eacd6
@ -127,3 +127,10 @@ UPDATE resource
|
||||
|
||||
ALTER TABLE notification_request ALTER COLUMN info SET DATA TYPE varchar(1000000);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS alarm_types (
|
||||
tenant_id uuid NOT NULL,
|
||||
type varchar(255) NOT NULL,
|
||||
CONSTRAINT tenant_id_type_unq_key UNIQUE (tenant_id, type)
|
||||
);
|
||||
|
||||
INSERT INTO alarm_types (tenant_id, type) SELECT DISTINCT tenant_id, type FROM alarm ON CONFLICT (tenant_id, type) DO NOTHING;
|
||||
|
||||
@ -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;
|
||||
@ -44,6 +45,7 @@ import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.EntityIdFactory;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.page.TimePageLink;
|
||||
import org.thingsboard.server.queue.util.TbCoreComponent;
|
||||
import org.thingsboard.server.service.entitiy.alarm.TbAlarmService;
|
||||
@ -498,4 +500,21 @@ 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 PageData<EntitySubtype> getAlarmTypes(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
|
||||
@RequestParam int pageSize,
|
||||
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
|
||||
@RequestParam int page,
|
||||
@ApiParam(value = ALARM_QUERY_TEXT_SEARCH_DESCRIPTION)
|
||||
@RequestParam(required = false) String textSearch,
|
||||
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
|
||||
@RequestParam(required = false) String sortOrder) throws ThingsboardException, ExecutionException, InterruptedException {
|
||||
PageLink pageLink = createPageLink(pageSize, page, textSearch, "type", sortOrder);
|
||||
return checkNotNull(alarmService.findAlarmTypesByTenantId(getTenantId(), pageLink));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
@ -46,6 +47,7 @@ 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.page.PageLink;
|
||||
import org.thingsboard.server.common.data.query.AlarmData;
|
||||
import org.thingsboard.server.common.data.query.AlarmDataQuery;
|
||||
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
|
||||
@ -235,6 +237,11 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<EntitySubtype> findAlarmTypesByTenantId(TenantId tenantId, PageLink pageLink) {
|
||||
return alarmService.findAlarmTypesByTenantId(tenantId, pageLink);
|
||||
}
|
||||
|
||||
private void onAlarmUpdated(AlarmApiCallResult result) {
|
||||
wsCallBackExecutor.submit(() -> {
|
||||
AlarmInfo alarm = result.getAlarm();
|
||||
|
||||
@ -39,7 +39,9 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
|
||||
import org.thingsboard.server.service.action.EntityActionService;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@TbCoreComponent
|
||||
@ -86,20 +88,25 @@ public class AlarmsCleanUpService {
|
||||
|
||||
PageLink removalBatchRequest = new PageLink(removalBatchSize, 0);
|
||||
long totalRemoved = 0;
|
||||
Set<String> typesToRemove = new HashSet<>();
|
||||
while (true) {
|
||||
PageData<AlarmId> toRemove = alarmDao.findAlarmsIdsByEndTsBeforeAndTenantId(expirationTime, tenantId, removalBatchRequest);
|
||||
for (AlarmId alarmId : toRemove.getData()) {
|
||||
relationService.deleteEntityRelations(tenantId, alarmId);
|
||||
Alarm alarm = alarmService.delAlarm(tenantId, alarmId).getAlarm();
|
||||
Alarm alarm = alarmService.delAlarm(tenantId, alarmId, false).getAlarm();
|
||||
if (alarm != null) {
|
||||
entityActionService.pushEntityActionToRuleEngine(alarm.getOriginator(), alarm, tenantId, null, ActionType.ALARM_DELETE, null);
|
||||
totalRemoved++;
|
||||
typesToRemove.add(alarm.getType());
|
||||
}
|
||||
}
|
||||
if (!toRemove.hasNext()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
alarmService.delAlarmTypes(tenantId, typesToRemove);
|
||||
|
||||
if (totalRemoved > 0) {
|
||||
log.info("Removed {} outdated alarm(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime));
|
||||
}
|
||||
|
||||
@ -507,6 +507,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:
|
||||
|
||||
@ -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,11 @@ 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.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;
|
||||
@ -796,4 +800,101 @@ public class AlarmControllerTest extends AbstractControllerTest {
|
||||
doPost("/api/alarm", alarm).andExpect(status().isForbidden());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAlarmTypes() throws Exception {
|
||||
loginTenantAdmin();
|
||||
|
||||
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?pageSize=1024&page=0", new TypeReference<PageData<EntitySubtype>>() {
|
||||
})
|
||||
.getData()
|
||||
.stream()
|
||||
.map(EntitySubtype::getType)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Collections.sort(types);
|
||||
Collections.sort(foundTypes);
|
||||
|
||||
Assert.assertEquals(types, foundTypes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteAlarmTypes() throws Exception {
|
||||
loginTenantAdmin();
|
||||
|
||||
List<AlarmInfo> alarms = new ArrayList<>();
|
||||
|
||||
for (int i = 1; i < 13; i++) {
|
||||
alarms.add(createAlarm(TEST_ALARM_TYPE + i));
|
||||
}
|
||||
|
||||
Device device = new Device();
|
||||
device.setName("Test device 2");
|
||||
device.setCustomerId(customerId);
|
||||
customerDevice = doPost("/api/device", device, Device.class);
|
||||
|
||||
for (int i = 1; i < 14; i++) {
|
||||
alarms.add(createAlarm(TEST_ALARM_TYPE + i));
|
||||
}
|
||||
|
||||
List<String> expectedTypes = alarms.stream().map(AlarmInfo::getType).distinct().sorted().collect(Collectors.toList());
|
||||
|
||||
List<String> foundTypes = doGetTyped("/api/alarm/types?pageSize=1024&page=0", new TypeReference<PageData<EntitySubtype>>() {
|
||||
})
|
||||
.getData()
|
||||
.stream()
|
||||
.map(EntitySubtype::getType)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Assert.assertEquals(13, foundTypes.size());
|
||||
Assert.assertEquals(expectedTypes, foundTypes);
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
doDelete("/api/alarm/" + alarms.get(i).getId()).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
foundTypes = doGetTyped("/api/alarm/types?pageSize=1024&page=0", new TypeReference<PageData<EntitySubtype>>() {
|
||||
})
|
||||
.getData()
|
||||
.stream()
|
||||
.map(EntitySubtype::getType)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Assert.assertEquals(13, foundTypes.size());
|
||||
Assert.assertEquals(expectedTypes, foundTypes);
|
||||
|
||||
doDelete("/api/alarm/" + alarms.get(12).getId()).andExpect(status().isOk());
|
||||
|
||||
foundTypes = doGetTyped("/api/alarm/types?pageSize=1024&page=0", new TypeReference<PageData<EntitySubtype>>() {
|
||||
})
|
||||
.getData()
|
||||
.stream()
|
||||
.map(EntitySubtype::getType)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Assert.assertEquals(12, foundTypes.size());
|
||||
|
||||
for (int i = 13; i < alarms.size(); i++) {
|
||||
doDelete("/api/alarm/" + alarms.get(i).getId()).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
foundTypes = doGetTyped("/api/alarm/types?pageSize=1024&page=0", new TypeReference<PageData<EntitySubtype>>() {
|
||||
})
|
||||
.getData()
|
||||
.stream()
|
||||
.map(EntitySubtype::getType)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Assert.assertTrue(foundTypes.isEmpty());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -104,10 +104,10 @@ public class AlarmsCleanUpServiceTest extends AbstractControllerTest {
|
||||
alarmsCleanUpService.cleanUp();
|
||||
|
||||
for (AlarmId outdatedAlarm : outdatedAlarms) {
|
||||
verify(alarmService).delAlarm(eq(tenantId), eq(outdatedAlarm));
|
||||
verify(alarmService).delAlarm(eq(tenantId), eq(outdatedAlarm), eq(false));
|
||||
}
|
||||
for (AlarmId freshAlarm : freshAlarms) {
|
||||
verify(alarmService, never()).delAlarm(eq(tenantId), eq(freshAlarm));
|
||||
verify(alarmService, never()).delAlarm(eq(tenantId), eq(freshAlarm), eq(false));
|
||||
}
|
||||
|
||||
verify(cleanUpServiceLogger).info(startsWith("Removed {} outdated alarm"), eq((long) count), eq(tenantId), any());
|
||||
|
||||
@ -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;
|
||||
@ -33,12 +34,14 @@ 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.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.query.AlarmCountQuery;
|
||||
import org.thingsboard.server.common.data.query.AlarmData;
|
||||
import org.thingsboard.server.common.data.query.AlarmDataQuery;
|
||||
import org.thingsboard.server.dao.entity.EntityDaoService;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public interface AlarmService extends EntityDaoService {
|
||||
@ -74,6 +77,10 @@ public interface AlarmService extends EntityDaoService {
|
||||
|
||||
AlarmApiCallResult delAlarm(TenantId tenantId, AlarmId alarmId);
|
||||
|
||||
AlarmApiCallResult delAlarm(TenantId tenantId, AlarmId alarmId, boolean deleteAlarmType);
|
||||
|
||||
void delAlarmTypes(TenantId tenantId, Set<String> types);
|
||||
|
||||
/*
|
||||
* Legacy API, before 3.5.
|
||||
*/
|
||||
@ -121,4 +128,6 @@ public interface AlarmService extends EntityDaoService {
|
||||
void deleteEntityAlarmRelations(TenantId tenantId, EntityId entityId);
|
||||
|
||||
long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query);
|
||||
|
||||
PageData<EntitySubtype> findAlarmTypesByTenantId(TenantId tenantId, PageLink pageLink);
|
||||
}
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -20,13 +20,14 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ApiModel
|
||||
public class PageData<T> {
|
||||
public class PageData<T> implements Serializable {
|
||||
|
||||
private final List<T> data;
|
||||
private final int totalPages;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,7 @@ public interface AlarmDao extends Dao<Alarm> {
|
||||
|
||||
long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query);
|
||||
|
||||
PageData<EntitySubtype> findTenantAlarmTypes(UUID tenantId, PageLink pageLink);
|
||||
|
||||
boolean removeAlarmTypes(UUID tenantId, Set<String> types);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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 org.thingsboard.server.common.data.page.PageData;
|
||||
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
|
||||
@Service("AlarmTypesCache")
|
||||
public class AlarmTypesCaffeineCache extends CaffeineTbTransactionalCache<TenantId, PageData<EntitySubtype>> {
|
||||
|
||||
public AlarmTypesCaffeineCache(CacheManager cacheManager) {
|
||||
super(cacheManager, CacheConstants.ALARM_TYPES_CACHE);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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 org.thingsboard.server.common.data.page.PageData;
|
||||
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
|
||||
@Service("AlarmTypesCache")
|
||||
public class AlarmTypesRedisCache extends RedisTbTransactionalCache<TenantId, PageData<EntitySubtype>> {
|
||||
|
||||
public AlarmTypesRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
|
||||
super(CacheConstants.ALARM_TYPES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());
|
||||
}
|
||||
}
|
||||
@ -24,7 +24,9 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.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;
|
||||
@ -48,6 +50,8 @@ import org.thingsboard.server.common.data.id.HasId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.page.SortOrder;
|
||||
import org.thingsboard.server.common.data.query.AlarmCountQuery;
|
||||
import org.thingsboard.server.common.data.query.AlarmData;
|
||||
import org.thingsboard.server.common.data.query.AlarmDataQuery;
|
||||
@ -55,7 +59,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.eventsourcing.ActionEntityEvent;
|
||||
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
|
||||
@ -82,15 +86,24 @@ 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, PageData<EntitySubtype>, AlarmTypesCacheEvictEvent> implements AlarmService {
|
||||
|
||||
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
|
||||
|
||||
private static final PageLink DEFAULT_ALARM_TYPES_PAGE_LINK = new PageLink(25, 0, null, new SortOrder("type"));
|
||||
|
||||
private final TenantService tenantService;
|
||||
private final AlarmDao alarmDao;
|
||||
private final EntityService entityService;
|
||||
private final DataValidator<Alarm> alarmDataValidator;
|
||||
|
||||
@TransactionalEventListener(classes = AlarmTypesCacheEvictEvent.class)
|
||||
@Override
|
||||
public void handleEvictEvent(AlarmTypesCacheEvictEvent event) {
|
||||
TenantId tenantId = event.getTenantId();
|
||||
cache.evict(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) {
|
||||
validateAlarmRequest(request);
|
||||
@ -124,6 +137,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
|
||||
if (result.getAlarm() != null) {
|
||||
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getAlarm().getTenantId())
|
||||
.entityId(result.getAlarm().getId()).added(true).build());
|
||||
publishEvictEvent(new AlarmTypesCacheEvictEvent(request.getTenantId()));
|
||||
}
|
||||
return withPropagated(result);
|
||||
}
|
||||
@ -204,6 +218,12 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
|
||||
@Override
|
||||
@Transactional
|
||||
public AlarmApiCallResult delAlarm(TenantId tenantId, AlarmId alarmId) {
|
||||
return delAlarm(tenantId, alarmId, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public AlarmApiCallResult delAlarm(TenantId tenantId, AlarmId alarmId, boolean deleteAlarmType) {
|
||||
log.debug("Deleting Alarm Id: {}", alarmId);
|
||||
AlarmInfo alarm = alarmDao.findAlarmInfoById(tenantId, alarmId.getId());
|
||||
if (alarm == null) {
|
||||
@ -213,10 +233,20 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
|
||||
alarmDao.removeById(tenantId, alarm.getUuidId());
|
||||
eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId)
|
||||
.entityId(alarmId).entity(alarm).build());
|
||||
if (deleteAlarmType) {
|
||||
delAlarmTypes(tenantId, Collections.singleton(alarm.getType()));
|
||||
}
|
||||
return AlarmApiCallResult.builder().alarm(alarm).deleted(true).successful(true).build();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delAlarmTypes(TenantId tenantId, Set<String> types) {
|
||||
if (!types.isEmpty() && alarmDao.removeAlarmTypes(tenantId.getId(), types)) {
|
||||
publishEvictEvent(new AlarmTypesCacheEvictEvent(tenantId));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId) {
|
||||
@ -411,6 +441,17 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
|
||||
return alarmDao.countAlarmsByQuery(tenantId, customerId, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<EntitySubtype> findAlarmTypesByTenantId(TenantId tenantId, PageLink pageLink) {
|
||||
log.trace("Executing findAlarmTypesByTenantId, tenantId [{}]", tenantId);
|
||||
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
|
||||
if (DEFAULT_ALARM_TYPES_PAGE_LINK.equals(pageLink)) {
|
||||
return cache.getAndPutInTransaction(tenantId, () ->
|
||||
alarmDao.findTenantAlarmTypes(tenantId.getId(), pageLink), false);
|
||||
}
|
||||
return alarmDao.findTenantAlarmTypes(tenantId.getId(), pageLink);
|
||||
}
|
||||
|
||||
private Alarm merge(Alarm existing, Alarm alarm) {
|
||||
if (alarm.getStartTs() > existing.getEndTs()) {
|
||||
existing.setEndTs(alarm.getStartTs());
|
||||
|
||||
@ -18,8 +18,10 @@ package org.thingsboard.server.dao.sql.alarm;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
|
||||
import org.thingsboard.server.dao.model.sql.AlarmEntity;
|
||||
import org.thingsboard.server.dao.model.sql.AlarmInfoEntity;
|
||||
@ -345,4 +347,13 @@ 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 at.type FROM alarm_types AS at WHERE at.tenant_id = :tenantId AND LOWER(at.type) LIKE LOWER(CONCAT('%', :searchText, '%'))", nativeQuery = true)
|
||||
Page<String> findTenantAlarmTypes(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, Pageable pageable);
|
||||
|
||||
@Transactional
|
||||
@Modifying
|
||||
@Query(value = "DELETE FROM alarm_types AS at WHERE NOT EXISTS (SELECT 1 FROM alarm AS a WHERE a.tenant_id = at.tenant_id AND a.type = at.type) AND at.tenant_id = :tenantId AND at.type IN (:types)", nativeQuery = true)
|
||||
int deleteTypeIfNoOneAlarmExists(@Param("tenantId") UUID tenantId, @Param("types") Set<String> types);
|
||||
|
||||
}
|
||||
|
||||
@ -19,11 +19,13 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
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 +69,9 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityTypesToDto;
|
||||
import static org.thingsboard.server.dao.DaoUtil.toPageable;
|
||||
|
||||
/**
|
||||
* Created by Valerii Sosliuk on 5/19/2017.
|
||||
*/
|
||||
@ -366,6 +371,22 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
|
||||
return alarmQueryRepository.countAlarmsByQuery(tenantId, customerId, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<EntitySubtype> findTenantAlarmTypes(UUID tenantId, PageLink pageLink) {
|
||||
Page<String> page = alarmRepository.findTenantAlarmTypes(tenantId, Objects.toString(pageLink.getTextSearch(), ""), toPageable(pageLink));
|
||||
if (page.isEmpty()) {
|
||||
return new PageData<>();
|
||||
}
|
||||
|
||||
List<EntitySubtype> data = convertTenantEntityTypesToDto(tenantId, EntityType.ALARM, page.getContent());
|
||||
return new PageData<>(data, page.getTotalPages(), page.getTotalElements(), page.hasNext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAlarmTypes(UUID tenantId, Set<String> types) {
|
||||
return alarmRepository.deleteTypeIfNoOneAlarmExists(tenantId, types) > 0;
|
||||
}
|
||||
|
||||
private static String getPropagationTypes(AlarmPropagationInfo ap) {
|
||||
String propagateRelationTypes;
|
||||
if (!CollectionUtils.isEmpty(ap.getPropagateRelationTypes())) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
@ -193,17 +193,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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -64,6 +64,12 @@ CREATE TABLE IF NOT EXISTS alarm (
|
||||
cleared boolean
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS alarm_types (
|
||||
tenant_id uuid NOT NULL,
|
||||
type varchar(255) NOT NULL,
|
||||
CONSTRAINT tenant_id_type_unq_key UNIQUE (tenant_id, type)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS alarm_comment (
|
||||
id uuid NOT NULL,
|
||||
created_time bigint NOT NULL,
|
||||
|
||||
@ -114,6 +114,7 @@ BEGIN
|
||||
a_details,
|
||||
a_propagate, a_propagate_to_owner, a_propagate_to_tenant, a_propagation_types,
|
||||
false, 0, false, 0, NULL, 0);
|
||||
INSERT INTO alarm_types (tenant_id, type) VALUES (t_id, a_type) ON CONFLICT (tenant_id, type) DO NOTHING;
|
||||
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
|
||||
RETURN json_build_object('success', true, 'created', true, 'modified', true, 'alarm', row_to_json(result))::text;
|
||||
ELSE
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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;
|
||||
@ -34,6 +35,7 @@ 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.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.query.AlarmData;
|
||||
import org.thingsboard.server.common.data.query.AlarmDataQuery;
|
||||
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
|
||||
@ -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);
|
||||
|
||||
PageData<EntitySubtype> findAlarmTypesByTenantId(TenantId tenantId, PageLink pageLink);
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -72,24 +72,11 @@
|
||||
</mat-chip-option>
|
||||
</mat-chip-listbox>
|
||||
</div>
|
||||
<div class="tb-form-row column-xs">
|
||||
<div class="fixed-title-width" translate>alarm.alarm-type-list</div>
|
||||
<mat-form-field fxFlex appearance="outline"
|
||||
subscriptSizing="dynamic"
|
||||
class="tb-chips">
|
||||
<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>
|
||||
<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>
|
||||
<tb-entity-subtype-list subscriptSizing="dynamic" appearance="outline" additionalClasses="tb-chips"
|
||||
[entityType]="entityType.ALARM" formControlName="typeList">
|
||||
</tb-entity-subtype-list>
|
||||
</div>
|
||||
<div class="tb-form-row column-xs">
|
||||
<div class="fixed-title-width" translate>alarm.assignee</div>
|
||||
|
||||
@ -40,12 +40,11 @@ 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 { Subscription } from 'rxjs';
|
||||
import { UtilsService } from '@core/services/utils.service';
|
||||
import { EntityType } from '@shared/models/entity-type.models';
|
||||
|
||||
export const ALARM_FILTER_CONFIG_DATA = new InjectionToken<any>('AlarmFilterConfigData');
|
||||
|
||||
@ -93,8 +92,6 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
|
||||
|
||||
panelMode = false;
|
||||
|
||||
readonly separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON];
|
||||
|
||||
alarmSearchStatuses = [AlarmSearchStatus.ACTIVE,
|
||||
AlarmSearchStatus.CLEARED,
|
||||
AlarmSearchStatus.ACK,
|
||||
@ -115,6 +112,8 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
|
||||
|
||||
panelResult: AlarmFilterConfig = null;
|
||||
|
||||
entityType = EntityType;
|
||||
|
||||
private alarmFilterConfig: AlarmFilterConfig;
|
||||
private resizeWindows: Subscription;
|
||||
|
||||
@ -263,40 +262,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,
|
||||
|
||||
@ -16,7 +16,8 @@
|
||||
|
||||
-->
|
||||
<mat-form-field [formGroup]="entitySubtypeListFormGroup" [floatLabel]="floatLabel"
|
||||
[class]="label ? '' : 'tb-chip-list'" class="mat-block">
|
||||
[appearance]="appearance" [subscriptSizing]="subscriptSizing"
|
||||
[class.tb-chip-list]="!label" [class]="additionalClasses" class="mat-block">
|
||||
<mat-label *ngIf="label">{{ label }}</mat-label>
|
||||
<mat-chip-grid #chipList formControlName="entitySubtypeList">
|
||||
<mat-chip-row
|
||||
|
||||
@ -23,14 +23,16 @@ import { AppState } from '@app/core/core.state';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { EntitySubtype, EntityType } from '@shared/models/entity-type.models';
|
||||
import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||
import { MatChipGrid, MatChipInputEvent } from '@angular/material/chips';
|
||||
import { MatChipInputEvent, MatChipGrid } from '@angular/material/chips';
|
||||
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 { coerceBoolean } from '@shared/decorators/coercion';
|
||||
import { AlarmService } from '@core/http/alarm.service';
|
||||
import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
|
||||
import { coerceArray, coerceBoolean } from '@shared/decorators/coercion';
|
||||
import { FloatLabelType } from '@angular/material/form-field';
|
||||
|
||||
@Component({
|
||||
@ -84,6 +86,16 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit,
|
||||
@Input()
|
||||
filledInputPlaceholder: string;
|
||||
|
||||
@Input()
|
||||
appearance: MatFormFieldAppearance = 'fill';
|
||||
|
||||
@Input()
|
||||
subscriptSizing: SubscriptSizing = 'fixed';
|
||||
|
||||
@Input()
|
||||
@coerceArray()
|
||||
additionalClasses: Array<string>;
|
||||
|
||||
@ViewChild('entitySubtypeInput') entitySubtypeInput: ElementRef<HTMLInputElement>;
|
||||
@ViewChild('entitySubtypeAutocomplete') entitySubtypeAutocomplete: MatAutocomplete;
|
||||
@ViewChild('chipList', {static: true}) chipList: MatChipGrid;
|
||||
@ -114,6 +126,7 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit,
|
||||
private deviceService: DeviceService,
|
||||
private edgeService: EdgeService,
|
||||
private entityViewService: EntityViewService,
|
||||
private alarmService: AlarmService,
|
||||
private fb: FormBuilder) {
|
||||
this.entitySubtypeListFormGroup = this.fb.group({
|
||||
entitySubtypeList: [this.entitySubtypeList, this.required ? [Validators.required] : []],
|
||||
@ -176,6 +189,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;
|
||||
}
|
||||
|
||||
if (this.emptyInputPlaceholder) {
|
||||
@ -295,6 +318,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(
|
||||
|
||||
@ -549,7 +549,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...",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user