38 changed files with 1425 additions and 700 deletions
@ -0,0 +1,259 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.controller; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import io.swagger.v3.oas.annotations.Parameter; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.http.HttpStatus; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.DeleteMapping; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.PathVariable; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.bind.annotation.ResponseStatus; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.EventInfo; |
|||
import org.thingsboard.server.common.data.cf.AlarmRuleDefinition; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
|||
import org.thingsboard.server.common.data.cf.AlarmRuleDefinitionInfo; |
|||
import org.thingsboard.server.common.data.cf.CalculatedField; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldFilter; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldInfo; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldType; |
|||
import org.thingsboard.server.common.data.event.EventType; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.CalculatedFieldId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.EntityIdFactory; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.config.annotations.ApiOperation; |
|||
import org.thingsboard.server.dao.event.EventService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.entitiy.cf.TbCalculatedFieldService; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
|
|||
import java.util.EnumSet; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.thingsboard.server.controller.ControllerConstants.CF_TEXT_SEARCH_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END; |
|||
import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_START; |
|||
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; |
|||
import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; |
|||
|
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequestMapping("/api") |
|||
@RequiredArgsConstructor |
|||
public class AlarmRuleController extends BaseController { |
|||
|
|||
private final TbCalculatedFieldService tbCalculatedFieldService; |
|||
private final EventService eventService; |
|||
|
|||
public static final String ALARM_RULE_ID = "alarmRuleId"; |
|||
|
|||
private static final String TEST_SCRIPT_EXPRESSION = |
|||
"Execute the alarm rule TBEL condition expression and return the result. " + |
|||
"Alarm rule expressions must return a boolean value. The format of request: \n\n" |
|||
+ MARKDOWN_CODE_BLOCK_START |
|||
+ "{\n" + |
|||
" \"expression\": \"return temperature > 50;\",\n" + |
|||
" \"arguments\": {\n" + |
|||
" \"temperature\": { \"type\": \"SINGLE_VALUE\", \"ts\": 1739776478057, \"value\": 55 }\n" + |
|||
" }\n" + |
|||
"}" |
|||
+ MARKDOWN_CODE_BLOCK_END |
|||
+ "\n\n Expected result JSON contains \"output\" and \"error\"."; |
|||
|
|||
@ApiOperation(value = "Create Or Update Alarm Rule (saveAlarmRule)", |
|||
notes = "Creates or Updates the Alarm Rule. When creating alarm rule, platform generates Alarm Rule Id as " + UUID_WIKI_LINK + |
|||
"The newly created Alarm Rule Id will be present in the response. " + |
|||
"Specify existing Alarm Rule Id to update the alarm rule. " + |
|||
"Referencing non-existing Alarm Rule Id will cause 'Not Found' error. " + |
|||
"Remove 'id', 'tenantId' from the request body example (below) to create new Alarm Rule entity. " |
|||
+ TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@PostMapping("/alarm/rule") |
|||
public AlarmRuleDefinition saveAlarmRule(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the alarm rule.") |
|||
@RequestBody AlarmRuleDefinition alarmRuleDefinition) throws Exception { |
|||
alarmRuleDefinition.setTenantId(getTenantId()); |
|||
checkEntityId(alarmRuleDefinition.getEntityId(), Operation.WRITE_CALCULATED_FIELD); |
|||
if (alarmRuleDefinition.getId() != null) { |
|||
checkAlarmRule(alarmRuleDefinition.getId()); |
|||
} |
|||
CalculatedField calculatedField = alarmRuleDefinition.toCalculatedField(); |
|||
checkReferencedEntities(calculatedField.getConfiguration()); |
|||
CalculatedField saved = tbCalculatedFieldService.save(calculatedField, getCurrentUser()); |
|||
return AlarmRuleDefinition.fromCalculatedField(saved); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get Alarm Rule (getAlarmRuleById)", |
|||
notes = "Fetch the Alarm Rule object based on the provided Alarm Rule Id." + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping("/alarm/rule/{alarmRuleId}") |
|||
public AlarmRuleDefinition getAlarmRuleById(@Parameter @PathVariable(ALARM_RULE_ID) String strAlarmRuleId) throws ThingsboardException { |
|||
checkParameter(ALARM_RULE_ID, strAlarmRuleId); |
|||
CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strAlarmRuleId)); |
|||
CalculatedField calculatedField = checkAlarmRule(calculatedFieldId); |
|||
checkEntityId(calculatedField.getEntityId(), Operation.READ_CALCULATED_FIELD); |
|||
return AlarmRuleDefinition.fromCalculatedField(calculatedField); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get Alarm Rules by Entity Id (getAlarmRulesByEntityId)", |
|||
notes = "Fetch the Alarm Rules based on the provided Entity Id." + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping(value = "/alarm/rules/{entityType}/{entityId}") |
|||
public PageData<AlarmRuleDefinition> getAlarmRulesByEntityId( |
|||
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, |
|||
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, |
|||
@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, |
|||
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, |
|||
@Parameter(description = CF_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, |
|||
@Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name"})) @RequestParam(required = false) String sortProperty, |
|||
@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
|||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
|||
checkParameter("entityId", entityIdStr); |
|||
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, entityIdStr); |
|||
checkEntityId(entityId, Operation.READ_CALCULATED_FIELD); |
|||
PageData<CalculatedField> result = checkNotNull(tbCalculatedFieldService.findByTenantIdAndEntityId(getTenantId(), entityId, CalculatedFieldType.ALARM, pageLink)); |
|||
return result.mapData(AlarmRuleDefinition::fromCalculatedField); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get alarm rules (getAlarmRules)", |
|||
notes = "Fetch tenant alarm rules based on the filter." + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping(value = "/alarm/rules") |
|||
public PageData<AlarmRuleDefinitionInfo> getAlarmRules(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) |
|||
@RequestParam int pageSize, |
|||
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) |
|||
@RequestParam int page, |
|||
@Parameter(description = "Entity type filter. If not specified, alarm rules for all supported entity types will be returned.") |
|||
@RequestParam(required = false) EntityType entityType, |
|||
@Parameter(description = "Entities filter. If not specified, alarm rules for entity type filter will be returned.") |
|||
@RequestParam(required = false) Set<UUID> entities, |
|||
@Parameter(description = CF_TEXT_SEARCH_DESCRIPTION) |
|||
@RequestParam(required = false) String textSearch, |
|||
@Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name"})) |
|||
@RequestParam(required = false) String sortProperty, |
|||
@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) |
|||
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
|||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
|||
SecurityUser user = getCurrentUser(); |
|||
|
|||
Set<EntityType> entityTypes; |
|||
if (entityType == null) { |
|||
entityTypes = CalculatedField.SUPPORTED_ENTITIES.entrySet().stream() |
|||
.filter(entry -> entry.getValue().contains(CalculatedFieldType.ALARM)) |
|||
.map(Map.Entry::getKey) |
|||
.collect(Collectors.toSet()); |
|||
} else { |
|||
entityTypes = EnumSet.of(entityType); |
|||
} |
|||
|
|||
CalculatedFieldFilter filter = CalculatedFieldFilter.builder() |
|||
.types(EnumSet.of(CalculatedFieldType.ALARM)) |
|||
.entityTypes(entityTypes) |
|||
.entityIds(entities) |
|||
.build(); |
|||
PageData<CalculatedFieldInfo> result = calculatedFieldService.findCalculatedFieldsByTenantIdAndFilter(user.getTenantId(), filter, pageLink); |
|||
return result.mapData(AlarmRuleDefinitionInfo::fromCalculatedFieldInfo); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get alarm rule names (getAlarmRuleNames)", |
|||
notes = "Fetch the list of alarm rule names." + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping(value = "/alarm/rules/names") |
|||
public PageData<String> getAlarmRuleNames(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) |
|||
@RequestParam int pageSize, |
|||
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) |
|||
@RequestParam int page, |
|||
@Parameter(description = CF_TEXT_SEARCH_DESCRIPTION) |
|||
@RequestParam(required = false) String textSearch, |
|||
@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) |
|||
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
|||
PageLink pageLink = createPageLink(pageSize, page, textSearch, "name", sortOrder); |
|||
return calculatedFieldService.findCalculatedFieldNamesByTenantIdAndType(getTenantId(), CalculatedFieldType.ALARM, pageLink); |
|||
} |
|||
|
|||
@ApiOperation(value = "Delete Alarm Rule (deleteAlarmRule)", |
|||
notes = "Deletes the alarm rule. Referencing non-existing Alarm Rule Id will cause an error." + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@DeleteMapping("/alarm/rule/{alarmRuleId}") |
|||
@ResponseStatus(HttpStatus.OK) |
|||
public void deleteAlarmRule(@PathVariable(ALARM_RULE_ID) String strAlarmRuleId) throws Exception { |
|||
checkParameter(ALARM_RULE_ID, strAlarmRuleId); |
|||
CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strAlarmRuleId)); |
|||
CalculatedField calculatedField = checkAlarmRule(calculatedFieldId); |
|||
checkEntityId(calculatedField.getEntityId(), Operation.WRITE_CALCULATED_FIELD); |
|||
tbCalculatedFieldService.delete(calculatedField, getCurrentUser()); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get latest alarm rule debug event (getLatestAlarmRuleDebugEvent)", |
|||
notes = "Gets latest alarm rule debug event for specified alarm rule id. " + |
|||
"Referencing non-existing alarm rule id will cause an error. " + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping("/alarm/rule/{alarmRuleId}/debug") |
|||
public JsonNode getLatestAlarmRuleDebugEvent(@Parameter @PathVariable(ALARM_RULE_ID) String strAlarmRuleId) throws ThingsboardException { |
|||
checkParameter(ALARM_RULE_ID, strAlarmRuleId); |
|||
CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strAlarmRuleId)); |
|||
CalculatedField calculatedField = checkAlarmRule(calculatedFieldId); |
|||
checkEntityId(calculatedField.getEntityId(), Operation.READ_CALCULATED_FIELD); |
|||
TenantId tenantId = getCurrentUser().getTenantId(); |
|||
return Optional.ofNullable(eventService.findLatestEvents(tenantId, calculatedFieldId, EventType.DEBUG_CALCULATED_FIELD, 1)) |
|||
.flatMap(events -> events.stream().map(EventInfo::getBody).findFirst()) |
|||
.orElse(null); |
|||
} |
|||
|
|||
@ApiOperation(value = "Test alarm rule TBEL expression (testAlarmRuleScript)", |
|||
notes = TEST_SCRIPT_EXPRESSION + TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@PostMapping("/alarm/rule/testScript") |
|||
public JsonNode testAlarmRuleScript( |
|||
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Test alarm rule TBEL condition expression. The expression must return a boolean value.") |
|||
@RequestBody JsonNode inputParams) throws ThingsboardException { |
|||
checkParameter("expression", inputParams.has("expression") ? inputParams.get("expression").asText() : null); |
|||
return tbCalculatedFieldService.executeTestScript(getTenantId(), inputParams); |
|||
} |
|||
|
|||
private CalculatedField checkAlarmRule(CalculatedFieldId calculatedFieldId) throws ThingsboardException { |
|||
CalculatedField calculatedField = tbCalculatedFieldService.findById(calculatedFieldId, getCurrentUser()); |
|||
checkNotNull(calculatedField); |
|||
if (calculatedField.getType() != CalculatedFieldType.ALARM) { |
|||
throw new ThingsboardException("Alarm rule not found", ThingsboardErrorCode.ITEM_NOT_FOUND); |
|||
} |
|||
return calculatedField; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,420 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.controller; |
|||
|
|||
import com.fasterxml.jackson.core.type.TypeReference; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import org.junit.After; |
|||
import org.junit.Before; |
|||
import org.junit.Test; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.Tenant; |
|||
import org.thingsboard.server.common.data.User; |
|||
import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
|||
import org.thingsboard.server.common.data.alarm.rule.AlarmRule; |
|||
import org.thingsboard.server.common.data.alarm.rule.condition.SimpleAlarmCondition; |
|||
import org.thingsboard.server.common.data.alarm.rule.condition.expression.TbelAlarmConditionExpression; |
|||
import org.thingsboard.server.common.data.cf.AlarmRuleDefinition; |
|||
import org.thingsboard.server.common.data.cf.AlarmRuleDefinitionInfo; |
|||
import org.thingsboard.server.common.data.cf.CalculatedField; |
|||
import org.thingsboard.server.common.data.cf.CalculatedFieldType; |
|||
import org.thingsboard.server.common.data.cf.configuration.AlarmCalculatedFieldConfiguration; |
|||
import org.thingsboard.server.common.data.cf.configuration.Argument; |
|||
import org.thingsboard.server.common.data.cf.configuration.ArgumentType; |
|||
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey; |
|||
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration; |
|||
import org.thingsboard.server.common.data.cf.configuration.TimeSeriesOutput; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
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.security.Authority; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.Comparator; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
|
|||
@DaoSqlTest |
|||
public class AlarmRuleControllerTest extends AbstractControllerTest { |
|||
|
|||
private Tenant savedTenant; |
|||
|
|||
@Before |
|||
public void beforeTest() throws Exception { |
|||
loginSysAdmin(); |
|||
|
|||
Tenant tenant = new Tenant(); |
|||
tenant.setTitle("My tenant"); |
|||
savedTenant = saveTenant(tenant); |
|||
assertThat(savedTenant).isNotNull(); |
|||
|
|||
User tenantAdmin = new User(); |
|||
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); |
|||
tenantAdmin.setTenantId(savedTenant.getId()); |
|||
tenantAdmin.setEmail("tenant2@thingsboard.org"); |
|||
tenantAdmin.setFirstName("Joe"); |
|||
tenantAdmin.setLastName("Downs"); |
|||
|
|||
createUserAndLogin(tenantAdmin, "testPassword1"); |
|||
} |
|||
|
|||
@After |
|||
public void afterTest() throws Exception { |
|||
loginSysAdmin(); |
|||
deleteTenant(savedTenant.getId()); |
|||
} |
|||
|
|||
@Test |
|||
public void testSaveAlarmRule() throws Exception { |
|||
Device testDevice = createDevice("Test device", "1234567890"); |
|||
AlarmRuleDefinition alarmRule = createTestAlarmRule(testDevice.getId(), "High Temperature"); |
|||
|
|||
AlarmRuleDefinition saved = saveAlarmRule(alarmRule); |
|||
|
|||
assertThat(saved).isNotNull(); |
|||
assertThat(saved.getId()).isNotNull(); |
|||
assertThat(saved.getCreatedTime()).isGreaterThan(0); |
|||
assertThat(saved.getTenantId()).isEqualTo(savedTenant.getId()); |
|||
assertThat(saved.getEntityId()).isEqualTo(testDevice.getId()); |
|||
assertThat(saved.getName()).isEqualTo("High Temperature"); |
|||
assertThat(saved.getConfiguration()).isNotNull(); |
|||
assertThat(saved.getConfiguration().getCreateRules()).containsKey(AlarmSeverity.CRITICAL); |
|||
|
|||
saved.setName("Updated Alarm Rule"); |
|||
AlarmRuleDefinition updated = saveAlarmRule(saved); |
|||
|
|||
assertThat(updated.getName()).isEqualTo("Updated Alarm Rule"); |
|||
assertThat(updated.getVersion()).isEqualTo(saved.getVersion() + 1); |
|||
|
|||
doDelete("/api/alarm/rule/" + saved.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRuleById() throws Exception { |
|||
Device testDevice = createDevice("Test device", "1234567890"); |
|||
AlarmRuleDefinition alarmRule = createTestAlarmRule(testDevice.getId(), "Test Alarm"); |
|||
|
|||
AlarmRuleDefinition saved = saveAlarmRule(alarmRule); |
|||
AlarmRuleDefinition fetched = doGet("/api/alarm/rule/" + saved.getId().getId(), AlarmRuleDefinition.class); |
|||
|
|||
assertThat(fetched).isNotNull(); |
|||
assertThat(fetched).isEqualTo(saved); |
|||
|
|||
doDelete("/api/alarm/rule/" + saved.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRuleById_notFound() throws Exception { |
|||
doGet("/api/alarm/rule/" + UUID.randomUUID()) |
|||
.andExpect(status().isNotFound()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRuleById_calculatedFieldNotAlarm() throws Exception { |
|||
Device testDevice = createDevice("Test device", "1234567890"); |
|||
CalculatedField cf = createSimpleCalculatedField(testDevice.getId()); |
|||
CalculatedField savedCf = doPost("/api/calculatedField", cf, CalculatedField.class); |
|||
|
|||
doGet("/api/alarm/rule/" + savedCf.getId().getId()) |
|||
.andExpect(status().isNotFound()); |
|||
|
|||
doDelete("/api/calculatedField/" + savedCf.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRulesByEntityId() throws Exception { |
|||
Device device1 = createDevice("Device 1", "1234567890"); |
|||
Device device2 = createDevice("Device 2", "0987654321"); |
|||
AlarmRuleDefinition rule1 = saveAlarmRule(createTestAlarmRule(device1.getId(), "Rule 1")); |
|||
saveAlarmRule(createTestAlarmRule(device2.getId(), "Rule 2")); |
|||
|
|||
PageData<AlarmRuleDefinition> result = doGetTypedWithPageLink( |
|||
"/api/alarm/rules/" + EntityType.DEVICE + "/" + device1.getUuidId() + "?", |
|||
new TypeReference<>() {}, new PageLink(10)); |
|||
|
|||
assertThat(result.getData()).hasSize(1); |
|||
assertThat(result.getData().get(0).getId()).isEqualTo(rule1.getId()); |
|||
assertThat(result.getData().get(0).getName()).isEqualTo("Rule 1"); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRules() throws Exception { |
|||
Device device = createDevice("Device A", "1234567890"); |
|||
AlarmRuleDefinition deviceRule = saveAlarmRule(createTestAlarmRule(device.getId(), "Device Alarm")); |
|||
|
|||
DeviceProfile profile = doPost("/api/deviceProfile", createDeviceProfile("Profile A"), DeviceProfile.class); |
|||
AlarmRuleDefinition profileRule = saveAlarmRule(createTestAlarmRule(profile.getId(), "Profile Alarm")); |
|||
|
|||
// All alarm rules
|
|||
List<AlarmRuleDefinitionInfo> all = getAlarmRules(null, null); |
|||
assertThat(all).extracting(AlarmRuleDefinition::getName) |
|||
.contains("Device Alarm", "Profile Alarm"); |
|||
|
|||
// Filter by entity type: DEVICE
|
|||
List<AlarmRuleDefinitionInfo> deviceRules = getAlarmRules(EntityType.DEVICE, null); |
|||
assertThat(deviceRules).extracting(AlarmRuleDefinition::getName) |
|||
.containsOnly("Device Alarm"); |
|||
|
|||
// Filter by entity type: DEVICE_PROFILE
|
|||
List<AlarmRuleDefinitionInfo> profileRules = getAlarmRules(EntityType.DEVICE_PROFILE, null); |
|||
assertThat(profileRules).extracting(AlarmRuleDefinition::getName) |
|||
.containsOnly("Profile Alarm"); |
|||
|
|||
// Filter by specific entity IDs
|
|||
List<AlarmRuleDefinitionInfo> specificRules = getAlarmRules(EntityType.DEVICE, List.of(device.getUuidId())); |
|||
assertThat(specificRules).extracting(AlarmRuleDefinition::getName) |
|||
.containsOnly("Device Alarm"); |
|||
|
|||
// Verify entity names are populated
|
|||
AlarmRuleDefinitionInfo deviceInfo = all.stream() |
|||
.filter(r -> r.getName().equals("Device Alarm")).findFirst().orElseThrow(); |
|||
assertThat(deviceInfo.getEntityName()).isEqualTo("Device A"); |
|||
|
|||
AlarmRuleDefinitionInfo profileInfo = all.stream() |
|||
.filter(r -> r.getName().equals("Profile Alarm")).findFirst().orElseThrow(); |
|||
assertThat(profileInfo.getEntityName()).isEqualTo("Profile A"); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRules_textSearch() throws Exception { |
|||
Device device = createDevice("Device A", "1234567890"); |
|||
saveAlarmRule(createTestAlarmRule(device.getId(), "Temperature Alarm")); |
|||
saveAlarmRule(createTestAlarmRule(device.getId(), "Humidity Alarm")); |
|||
|
|||
PageData<AlarmRuleDefinitionInfo> result = doGetTypedWithPageLink( |
|||
"/api/alarm/rules?textSearch=Temp&", |
|||
new TypeReference<>() {}, new PageLink(10)); |
|||
|
|||
assertThat(result.getData()).hasSize(1); |
|||
assertThat(result.getData().get(0).getName()).isEqualTo("Temperature Alarm"); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetAlarmRuleNames() throws Exception { |
|||
Device device = createDevice("Device A", "1234567890"); |
|||
saveAlarmRule(createTestAlarmRule(device.getId(), "Alpha Alarm")); |
|||
saveAlarmRule(createTestAlarmRule(device.getId(), "Beta Alarm")); |
|||
|
|||
PageData<String> names = getAlarmRuleNames(new PageLink(10, 0, |
|||
null, new SortOrder("", SortOrder.Direction.ASC))); |
|||
assertThat(names.getTotalElements()).isEqualTo(2); |
|||
assertThat(names.getData()).isSortedAccordingTo(Comparator.naturalOrder()); |
|||
assertThat(names.getData()).contains("Alpha Alarm", "Beta Alarm"); |
|||
|
|||
names = getAlarmRuleNames(new PageLink(10, 0, |
|||
null, new SortOrder("", SortOrder.Direction.DESC))); |
|||
assertThat(names.getData()).isSortedAccordingTo(Comparator.reverseOrder()); |
|||
|
|||
names = getAlarmRuleNames(new PageLink(10, 0, |
|||
"Alpha", new SortOrder("", SortOrder.Direction.ASC))); |
|||
assertThat(names.getTotalElements()).isEqualTo(1); |
|||
assertThat(names.getData()).containsOnly("Alpha Alarm"); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteAlarmRule() throws Exception { |
|||
Device testDevice = createDevice("Test device", "1234567890"); |
|||
AlarmRuleDefinition saved = saveAlarmRule(createTestAlarmRule(testDevice.getId(), "To Delete")); |
|||
|
|||
assertThat(saved).isNotNull(); |
|||
|
|||
doDelete("/api/alarm/rule/" + saved.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
doGet("/api/alarm/rule/" + saved.getId().getId()) |
|||
.andExpect(status().isNotFound()); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteAlarmRule_notFound() throws Exception { |
|||
doDelete("/api/alarm/rule/" + UUID.randomUUID()) |
|||
.andExpect(status().isNotFound()); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteAlarmRule_calculatedFieldNotAlarm() throws Exception { |
|||
Device testDevice = createDevice("Test device", "1234567890"); |
|||
CalculatedField cf = createSimpleCalculatedField(testDevice.getId()); |
|||
CalculatedField savedCf = doPost("/api/calculatedField", cf, CalculatedField.class); |
|||
|
|||
doDelete("/api/alarm/rule/" + savedCf.getId().getId()) |
|||
.andExpect(status().isNotFound()); |
|||
|
|||
doDelete("/api/calculatedField/" + savedCf.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetLatestAlarmRuleDebugEvent() throws Exception { |
|||
Device testDevice = createDevice("Test device", "1234567890"); |
|||
AlarmRuleDefinition saved = saveAlarmRule(createTestAlarmRule(testDevice.getId(), "Debug Test")); |
|||
|
|||
doGet("/api/alarm/rule/" + saved.getId().getId() + "/debug") |
|||
.andExpect(status().isOk()); |
|||
|
|||
doDelete("/api/alarm/rule/" + saved.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetLatestAlarmRuleDebugEvent_notFound() throws Exception { |
|||
doGet("/api/alarm/rule/" + UUID.randomUUID() + "/debug") |
|||
.andExpect(status().isNotFound()); |
|||
} |
|||
|
|||
@Test |
|||
public void testTestAlarmRuleScript() throws Exception { |
|||
JsonNode request = JacksonUtil.toJsonNode(""" |
|||
{ |
|||
"expression": "return temperature > 50;", |
|||
"arguments": { |
|||
"temperature": { "type": "SINGLE_VALUE", "ts": 1739776478057, "value": 55 } |
|||
} |
|||
} |
|||
"""); |
|||
|
|||
JsonNode result = doPost("/api/alarm/rule/testScript", request, JsonNode.class); |
|||
|
|||
assertThat(result).isNotNull(); |
|||
assertThat(result.has("output")).isTrue(); |
|||
assertThat(result.has("error")).isTrue(); |
|||
assertThat(result.get("error").asText()).isEmpty(); |
|||
assertThat(result.get("output").asText()).isEqualTo("true"); |
|||
} |
|||
|
|||
@Test |
|||
public void testTestAlarmRuleScript_returnsFalse() throws Exception { |
|||
JsonNode request = JacksonUtil.toJsonNode(""" |
|||
{ |
|||
"expression": "return temperature > 50;", |
|||
"arguments": { |
|||
"temperature": { "type": "SINGLE_VALUE", "ts": 1739776478057, "value": 30 } |
|||
} |
|||
} |
|||
"""); |
|||
|
|||
JsonNode result = doPost("/api/alarm/rule/testScript", request, JsonNode.class); |
|||
|
|||
assertThat(result).isNotNull(); |
|||
assertThat(result.get("error").asText()).isEmpty(); |
|||
assertThat(result.get("output").asText()).isEqualTo("false"); |
|||
} |
|||
|
|||
@Test |
|||
public void testTestAlarmRuleScript_missingExpression() throws Exception { |
|||
JsonNode request = JacksonUtil.toJsonNode(""" |
|||
{ |
|||
"arguments": {} |
|||
} |
|||
"""); |
|||
|
|||
doPost("/api/alarm/rule/testScript", request) |
|||
.andExpect(status().isBadRequest()); |
|||
} |
|||
|
|||
@Test |
|||
public void testTestAlarmRuleScript_invalidExpression() throws Exception { |
|||
JsonNode request = JacksonUtil.toJsonNode(""" |
|||
{ |
|||
"expression": "invalid syntax {{{{", |
|||
"arguments": {} |
|||
} |
|||
"""); |
|||
|
|||
JsonNode result = doPost("/api/alarm/rule/testScript", request, JsonNode.class); |
|||
|
|||
assertThat(result).isNotNull(); |
|||
assertThat(result.get("error").asText()).isNotEmpty(); |
|||
} |
|||
|
|||
// --- Helper methods ---
|
|||
|
|||
private AlarmRuleDefinition createTestAlarmRule(EntityId entityId, String name) { |
|||
AlarmRuleDefinition alarmRule = new AlarmRuleDefinition(); |
|||
alarmRule.setEntityId(entityId); |
|||
alarmRule.setName(name); |
|||
alarmRule.setConfigurationVersion(1); |
|||
alarmRule.setAdditionalInfo(JacksonUtil.newObjectNode()); |
|||
|
|||
AlarmCalculatedFieldConfiguration configuration = new AlarmCalculatedFieldConfiguration(); |
|||
|
|||
Argument argument = new Argument(); |
|||
argument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); |
|||
argument.setDefaultValue("0"); |
|||
configuration.setArguments(Map.of("temperature", argument)); |
|||
|
|||
AlarmRule rule = new AlarmRule(); |
|||
TbelAlarmConditionExpression expression = new TbelAlarmConditionExpression(); |
|||
expression.setExpression("return temperature >= 50;"); |
|||
SimpleAlarmCondition condition = new SimpleAlarmCondition(); |
|||
condition.setExpression(expression); |
|||
rule.setCondition(condition); |
|||
configuration.setCreateRules(Map.of(AlarmSeverity.CRITICAL, rule)); |
|||
|
|||
alarmRule.setConfiguration(configuration); |
|||
return alarmRule; |
|||
} |
|||
|
|||
private CalculatedField createSimpleCalculatedField(EntityId entityId) { |
|||
CalculatedField cf = new CalculatedField(); |
|||
cf.setEntityId(entityId); |
|||
cf.setType(CalculatedFieldType.SIMPLE); |
|||
cf.setName("Simple CF"); |
|||
cf.setConfigurationVersion(1); |
|||
cf.setAdditionalInfo(JacksonUtil.newObjectNode()); |
|||
|
|||
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration(); |
|||
Argument arg = new Argument(); |
|||
arg.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); |
|||
config.setArguments(Map.of("T", arg)); |
|||
config.setExpression("T * 2"); |
|||
TimeSeriesOutput output = new TimeSeriesOutput(); |
|||
output.setName("result"); |
|||
config.setOutput(output); |
|||
cf.setConfiguration(config); |
|||
|
|||
return cf; |
|||
} |
|||
|
|||
private List<AlarmRuleDefinitionInfo> getAlarmRules(EntityType entityType, List<UUID> entities) throws Exception { |
|||
StringBuilder url = new StringBuilder("/api/alarm/rules?"); |
|||
if (entityType != null) { |
|||
url.append("entityType=").append(entityType).append("&"); |
|||
} |
|||
if (entities != null) { |
|||
url.append("entities=").append(String.join(",", |
|||
entities.stream().map(UUID::toString).toList())).append("&"); |
|||
} |
|||
return doGetTypedWithPageLink(url.toString(), |
|||
new TypeReference<PageData<AlarmRuleDefinitionInfo>>() {}, new PageLink(10)).getData(); |
|||
} |
|||
|
|||
private PageData<String> getAlarmRuleNames(PageLink pageLink) throws Exception { |
|||
return doGetTypedWithPageLink("/api/alarm/rules/names?", |
|||
new TypeReference<PageData<String>>() {}, pageLink); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,166 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.common.data.cf; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
import com.fasterxml.jackson.annotation.JsonSetter; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import jakarta.validation.Valid; |
|||
import jakarta.validation.constraints.NotNull; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.BaseData; |
|||
import org.thingsboard.server.common.data.HasAdditionalInfo; |
|||
import org.thingsboard.server.common.data.HasDebugSettings; |
|||
import org.thingsboard.server.common.data.HasName; |
|||
import org.thingsboard.server.common.data.HasTenantId; |
|||
import org.thingsboard.server.common.data.HasVersion; |
|||
import org.thingsboard.server.common.data.cf.configuration.AlarmCalculatedFieldConfiguration; |
|||
import org.thingsboard.server.common.data.debug.DebugSettings; |
|||
import org.thingsboard.server.common.data.id.CalculatedFieldId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.validation.Length; |
|||
import org.thingsboard.server.common.data.validation.NoXss; |
|||
|
|||
@Schema |
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class AlarmRuleDefinition extends BaseData<CalculatedFieldId> implements HasName, HasTenantId, HasVersion, HasDebugSettings, HasAdditionalInfo { |
|||
|
|||
private TenantId tenantId; |
|||
private EntityId entityId; |
|||
|
|||
@NoXss |
|||
@Length(fieldName = "name") |
|||
@Schema(description = "User defined name of the alarm rule.") |
|||
private String name; |
|||
@Deprecated |
|||
@Schema(description = "Enable/disable debug. ", example = "false", deprecated = true) |
|||
private boolean debugMode; |
|||
@Schema(description = "Debug settings object.") |
|||
private DebugSettings debugSettings; |
|||
@Schema(description = "Version of alarm rule configuration.", example = "0") |
|||
private int configurationVersion; |
|||
@Schema(implementation = AlarmCalculatedFieldConfiguration.class) |
|||
@Valid |
|||
@NotNull |
|||
private AlarmCalculatedFieldConfiguration configuration; |
|||
private Long version; |
|||
@NoXss |
|||
@Schema(description = "Additional parameters of the alarm rule. " + |
|||
"May include: 'description' (string).", |
|||
implementation = com.fasterxml.jackson.databind.JsonNode.class, |
|||
example = "{\"description\":\"High temperature alarm rule\"}") |
|||
private JsonNode additionalInfo; |
|||
|
|||
public AlarmRuleDefinition() {} |
|||
|
|||
public AlarmRuleDefinition(CalculatedFieldId id) { |
|||
super(id); |
|||
} |
|||
|
|||
public AlarmRuleDefinition(AlarmRuleDefinition alarmRuleDefinition) { |
|||
super(alarmRuleDefinition); |
|||
this.tenantId = alarmRuleDefinition.tenantId; |
|||
this.entityId = alarmRuleDefinition.entityId; |
|||
this.name = alarmRuleDefinition.name; |
|||
this.debugMode = alarmRuleDefinition.debugMode; |
|||
this.debugSettings = alarmRuleDefinition.debugSettings; |
|||
this.configurationVersion = alarmRuleDefinition.configurationVersion; |
|||
this.configuration = alarmRuleDefinition.configuration; |
|||
this.version = alarmRuleDefinition.version; |
|||
this.additionalInfo = alarmRuleDefinition.additionalInfo; |
|||
} |
|||
|
|||
@Schema(description = "JSON object with the Alarm Rule Id. Referencing non-existing Alarm Rule Id will cause error.") |
|||
@Override |
|||
public CalculatedFieldId getId() { |
|||
return super.getId(); |
|||
} |
|||
|
|||
@Schema(description = "Timestamp of the alarm rule creation, in milliseconds", example = "1609459200000", accessMode = Schema.AccessMode.READ_ONLY) |
|||
@Override |
|||
public long getCreatedTime() { |
|||
return super.getCreatedTime(); |
|||
} |
|||
|
|||
// Getter is ignored for serialization
|
|||
@JsonIgnore |
|||
public boolean isDebugMode() { |
|||
return debugMode; |
|||
} |
|||
|
|||
// Setter is annotated for deserialization
|
|||
@JsonSetter |
|||
public void setDebugMode(boolean debugMode) { |
|||
this.debugMode = debugMode; |
|||
} |
|||
|
|||
public CalculatedField toCalculatedField() { |
|||
CalculatedField cf = new CalculatedField(); |
|||
cf.setId(this.id); |
|||
cf.setCreatedTime(this.createdTime); |
|||
cf.setTenantId(this.tenantId); |
|||
cf.setEntityId(this.entityId); |
|||
cf.setType(CalculatedFieldType.ALARM); |
|||
cf.setName(this.name); |
|||
cf.setDebugMode(this.debugMode); |
|||
cf.setDebugSettings(this.debugSettings); |
|||
cf.setConfigurationVersion(this.configurationVersion); |
|||
cf.setConfiguration(this.configuration); |
|||
cf.setVersion(this.version); |
|||
cf.setAdditionalInfo(this.additionalInfo); |
|||
return cf; |
|||
} |
|||
|
|||
public static AlarmRuleDefinition fromCalculatedField(CalculatedField cf) { |
|||
AlarmRuleDefinition def = new AlarmRuleDefinition(); |
|||
def.setId(cf.getId()); |
|||
def.setCreatedTime(cf.getCreatedTime()); |
|||
def.setTenantId(cf.getTenantId()); |
|||
def.setEntityId(cf.getEntityId()); |
|||
def.setName(cf.getName()); |
|||
def.setDebugMode(cf.isDebugMode()); |
|||
def.setDebugSettings(cf.getDebugSettings()); |
|||
def.setConfigurationVersion(cf.getConfigurationVersion()); |
|||
if (!(cf.getConfiguration() instanceof AlarmCalculatedFieldConfiguration config)) { |
|||
throw new IllegalArgumentException("Expected ALARM calculated field, got " + cf.getType()); |
|||
} |
|||
def.setConfiguration(config); |
|||
def.setVersion(cf.getVersion()); |
|||
def.setAdditionalInfo(cf.getAdditionalInfo()); |
|||
return def; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return new StringBuilder() |
|||
.append("AlarmRuleDefinition[") |
|||
.append("tenantId=").append(tenantId) |
|||
.append(", entityId=").append(entityId) |
|||
.append(", name='").append(name) |
|||
.append(", configurationVersion=").append(configurationVersion) |
|||
.append(", configuration=").append(configuration) |
|||
.append(", additionalInfo=").append(additionalInfo) |
|||
.append(", version=").append(version) |
|||
.append(", createdTime=").append(createdTime) |
|||
.append(", id=").append(id).append(']') |
|||
.toString(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.common.data.cf; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@NoArgsConstructor |
|||
public class AlarmRuleDefinitionInfo extends AlarmRuleDefinition { |
|||
|
|||
private String entityName; |
|||
|
|||
public AlarmRuleDefinitionInfo(AlarmRuleDefinition alarmRuleDefinition, String entityName) { |
|||
super(alarmRuleDefinition); |
|||
this.entityName = entityName; |
|||
} |
|||
|
|||
public static AlarmRuleDefinitionInfo fromCalculatedFieldInfo(CalculatedFieldInfo cfi) { |
|||
AlarmRuleDefinition def = AlarmRuleDefinition.fromCalculatedField(cfi); |
|||
return new AlarmRuleDefinitionInfo(def, cfi.getEntityName()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
///
|
|||
/// Copyright © 2016-2026 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.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { defaultHttpOptionsFromConfig, defaultHttpOptionsFromParams, RequestConfig } from './http-utils'; |
|||
import { Observable } from 'rxjs'; |
|||
import { HttpClient } from '@angular/common/http'; |
|||
import { PageData } from '@shared/models/page/page-data'; |
|||
import { |
|||
CalculatedFieldAlarmRule, |
|||
CalculatedFieldAlarmRuleInfo, |
|||
CalculatedFieldsQuery, |
|||
CalculatedFieldTestScriptInputParams |
|||
} from '@shared/models/calculated-field.models'; |
|||
import { PageLink } from '@shared/models/page/page-link'; |
|||
import { EntityId } from '@shared/models/id/entity-id'; |
|||
import { EntityTestScriptResult } from '@shared/models/entity.models'; |
|||
import { CalculatedFieldEventBody } from '@shared/models/event.models'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class AlarmRulesService { |
|||
|
|||
constructor( |
|||
private http: HttpClient |
|||
) { } |
|||
|
|||
public getAlarmRuleById(alarmRuleId: string, config?: RequestConfig): Observable<CalculatedFieldAlarmRule> { |
|||
return this.http.get<CalculatedFieldAlarmRule>(`/api/alarm/rule/${alarmRuleId}`, defaultHttpOptionsFromConfig(config)); |
|||
} |
|||
|
|||
public saveAlarmRule(alarmRule: CalculatedFieldAlarmRule, config?: RequestConfig): Observable<CalculatedFieldAlarmRule> { |
|||
return this.http.post<CalculatedFieldAlarmRule>('/api/alarm/rule', alarmRule, defaultHttpOptionsFromConfig(config)); |
|||
} |
|||
|
|||
public deleteAlarmRule(alarmRuleId: string, config?: RequestConfig): Observable<boolean> { |
|||
return this.http.delete<boolean>(`/api/alarm/rule/${alarmRuleId}`, defaultHttpOptionsFromConfig(config)); |
|||
} |
|||
|
|||
public getAlarmRules(pageLink: PageLink, query: CalculatedFieldsQuery, config?: RequestConfig): Observable<PageData<CalculatedFieldAlarmRuleInfo>> { |
|||
return this.http.get<PageData<CalculatedFieldAlarmRuleInfo>>(`/api/alarm/rules${pageLink.toQuery()}`, defaultHttpOptionsFromParams(query, config)); |
|||
} |
|||
|
|||
public getAlarmRulesByEntityId({ entityType, id }: EntityId, pageLink: PageLink, config?: RequestConfig): Observable<PageData<CalculatedFieldAlarmRule>> { |
|||
return this.http.get<PageData<CalculatedFieldAlarmRule>>(`/api/alarm/rules/${entityType}/${id}${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); |
|||
} |
|||
|
|||
public testScript(inputParams: CalculatedFieldTestScriptInputParams, config?: RequestConfig): Observable<EntityTestScriptResult> { |
|||
return this.http.post<EntityTestScriptResult>('/api/alarm/rule/testScript', inputParams, defaultHttpOptionsFromConfig(config)); |
|||
} |
|||
|
|||
public getLatestAlarmRuleDebugEvent(id: string, config?: RequestConfig): Observable<CalculatedFieldEventBody> { |
|||
return this.http.get<CalculatedFieldEventBody>(`/api/alarm/rule/${id}/debug`, defaultHttpOptionsFromConfig(config)); |
|||
} |
|||
|
|||
public getAlarmRuleNames(pageLink: PageLink, config?: RequestConfig): Observable<PageData<string>> { |
|||
return this.http.get<PageData<string>>(`/api/alarm/rules/names${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue