|
|
|
@ -44,6 +44,8 @@ import org.thingsboard.script.api.tbel.TbelInvokeService; |
|
|
|
import org.thingsboard.server.common.data.EntityType; |
|
|
|
import org.thingsboard.server.common.data.EventInfo; |
|
|
|
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.cf.configuration.CalculatedFieldConfiguration; |
|
|
|
import org.thingsboard.server.common.data.event.EventType; |
|
|
|
@ -59,14 +61,18 @@ import org.thingsboard.server.dao.event.EventService; |
|
|
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
|
|
|
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldTbelScriptEngine; |
|
|
|
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.ArrayList; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.EnumSet; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Objects; |
|
|
|
import java.util.Optional; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.UUID; |
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
|
|
|
|
import static org.thingsboard.server.controller.ControllerConstants.CF_TEXT_SEARCH_DESCRIPTION; |
|
|
|
@ -99,30 +105,30 @@ public class CalculatedFieldController extends BaseController { |
|
|
|
|
|
|
|
private static final String TEST_SCRIPT_EXPRESSION = |
|
|
|
"Execute the Script expression and return the result. The format of request: \n\n" |
|
|
|
+ MARKDOWN_CODE_BLOCK_START |
|
|
|
+ "{\n" + |
|
|
|
" \"expression\": \"var temp = 0; foreach(element: temperature.values) {temp += element.value;} var avgTemperature = temp / temperature.values.size(); var adjustedTemperature = avgTemperature + 0.1 * humidity.value; return {\\\"adjustedTemperature\\\": adjustedTemperature};\",\n" + |
|
|
|
" \"arguments\": {\n" + |
|
|
|
" \"temperature\": {\n" + |
|
|
|
" \"type\": \"TS_ROLLING\",\n" + |
|
|
|
" \"timeWindow\": {\n" + |
|
|
|
" \"startTs\": 1739775630002,\n" + |
|
|
|
" \"endTs\": 65432211,\n" + |
|
|
|
" \"limit\": 5\n" + |
|
|
|
" },\n" + |
|
|
|
" \"values\": [\n" + |
|
|
|
" { \"ts\": 1739775639851, \"value\": 23 },\n" + |
|
|
|
" { \"ts\": 1739775664561, \"value\": 43 },\n" + |
|
|
|
" { \"ts\": 1739775713079, \"value\": 15 },\n" + |
|
|
|
" { \"ts\": 1739775999522, \"value\": 34 },\n" + |
|
|
|
" { \"ts\": 1739776228452, \"value\": 22 }\n" + |
|
|
|
" ]\n" + |
|
|
|
" },\n" + |
|
|
|
" \"humidity\": { \"type\": \"SINGLE_VALUE\", \"ts\": 1739776478057, \"value\": 23 }\n" + |
|
|
|
" }\n" + |
|
|
|
"}" |
|
|
|
+ MARKDOWN_CODE_BLOCK_END |
|
|
|
+ "\n\n Expected result JSON contains \"output\" and \"error\"."; |
|
|
|
+ MARKDOWN_CODE_BLOCK_START |
|
|
|
+ "{\n" + |
|
|
|
" \"expression\": \"var temp = 0; foreach(element: temperature.values) {temp += element.value;} var avgTemperature = temp / temperature.values.size(); var adjustedTemperature = avgTemperature + 0.1 * humidity.value; return {\\\"adjustedTemperature\\\": adjustedTemperature};\",\n" + |
|
|
|
" \"arguments\": {\n" + |
|
|
|
" \"temperature\": {\n" + |
|
|
|
" \"type\": \"TS_ROLLING\",\n" + |
|
|
|
" \"timeWindow\": {\n" + |
|
|
|
" \"startTs\": 1739775630002,\n" + |
|
|
|
" \"endTs\": 65432211,\n" + |
|
|
|
" \"limit\": 5\n" + |
|
|
|
" },\n" + |
|
|
|
" \"values\": [\n" + |
|
|
|
" { \"ts\": 1739775639851, \"value\": 23 },\n" + |
|
|
|
" { \"ts\": 1739775664561, \"value\": 43 },\n" + |
|
|
|
" { \"ts\": 1739775713079, \"value\": 15 },\n" + |
|
|
|
" { \"ts\": 1739775999522, \"value\": 34 },\n" + |
|
|
|
" { \"ts\": 1739776228452, \"value\": 22 }\n" + |
|
|
|
" ]\n" + |
|
|
|
" },\n" + |
|
|
|
" \"humidity\": { \"type\": \"SINGLE_VALUE\", \"ts\": 1739776478057, \"value\": 23 }\n" + |
|
|
|
" }\n" + |
|
|
|
"}" |
|
|
|
+ MARKDOWN_CODE_BLOCK_END |
|
|
|
+ "\n\n Expected result JSON contains \"output\" and \"error\"."; |
|
|
|
|
|
|
|
@ApiOperation(value = "Create Or Update Calculated Field (saveCalculatedField)", |
|
|
|
notes = "Creates or Updates the Calculated Field. When creating calculated field, platform generates Calculated Field Id as " + UUID_WIKI_LINK + |
|
|
|
@ -183,6 +189,46 @@ public class CalculatedFieldController extends BaseController { |
|
|
|
return checkNotNull(tbCalculatedFieldService.findByTenantIdAndEntityId(getTenantId(), entityId, type, pageLink)); |
|
|
|
} |
|
|
|
|
|
|
|
@ApiOperation(value = "Get calculated fields (getCalculatedFields)", |
|
|
|
notes = "Fetch tenant calculated fields based on the filter.") |
|
|
|
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
|
|
|
@GetMapping(value = "/calculatedFields") |
|
|
|
public PageData<CalculatedFieldInfo> getCalculatedFields(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) |
|
|
|
@RequestParam int pageSize, |
|
|
|
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) |
|
|
|
@RequestParam int page, |
|
|
|
@Parameter(description = "Calculated field type filter.") |
|
|
|
@RequestParam CalculatedFieldType type, |
|
|
|
@Parameter(description = "Entity type filter. If not specified, calculated fields for all supported entity types will be returned.") |
|
|
|
@RequestParam(required = false) EntityType entityType, |
|
|
|
@Parameter(description = "Entities filter. If not specified, calculated fields for entity type filter will be returned.") |
|
|
|
@RequestParam(required = false) List<UUID> entities, |
|
|
|
@Parameter(description = "Name filter.") |
|
|
|
@RequestParam(required = false) String name, |
|
|
|
@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.keySet(); |
|
|
|
} else { |
|
|
|
entityTypes = EnumSet.of(entityType); |
|
|
|
} |
|
|
|
|
|
|
|
CalculatedFieldFilter filter = CalculatedFieldFilter.builder() |
|
|
|
.type(type) |
|
|
|
.entityTypes(entityTypes) |
|
|
|
.entityIds(entities) |
|
|
|
.name(name) |
|
|
|
.build(); |
|
|
|
return calculatedFieldService.findCalculatedFieldsByTenantIdAndFilter(user.getTenantId(), filter, pageLink); |
|
|
|
} |
|
|
|
|
|
|
|
@ApiOperation(value = "Delete Calculated Field (deleteCalculatedField)", |
|
|
|
notes = "Deletes the calculated field. Referencing non-existing Calculated Field Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) |
|
|
|
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|
|
|
@ -295,7 +341,8 @@ public class CalculatedFieldController extends BaseController { |
|
|
|
return; |
|
|
|
} |
|
|
|
case CUSTOMER, ASSET, DEVICE -> checkEntityId(referencedEntityId, Operation.READ); |
|
|
|
default -> throw new IllegalArgumentException("Calculated fields do not support '" + entityType + "' for referenced entities."); |
|
|
|
default -> |
|
|
|
throw new IllegalArgumentException("Calculated fields do not support '" + entityType + "' for referenced entities."); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|