Browse Source

Rule chains refactoring:

pull/2818/head
Volodymyr Babak 7 years ago
parent
commit
c409e2d9c1
  1. 9
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  2. 74
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  3. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
  4. 12
      common/edge-api/src/main/proto/edge.proto
  5. 8
      dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java
  6. 16
      dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java
  7. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  8. 12
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java
  9. 12
      dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java
  10. 15
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  11. 17
      dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java
  12. 11
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
  13. 14
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
  14. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java
  15. 14
      dao/src/main/resources/cassandra/schema-entities.cql
  16. 2
      ui/src/app/api/entity.service.js
  17. 5
      ui/src/app/api/rule-chain.service.js
  18. 2
      ui/src/app/common/types.constant.js
  19. 3
      ui/src/app/locale/locale.constant-en_US.json
  20. 2
      ui/src/app/rulechain/add-rulechain.tpl.html
  21. 2
      ui/src/app/rulechain/add-rulenode.tpl.html
  22. 8
      ui/src/app/rulechain/rulechain-fieldset.tpl.html
  23. 9
      ui/src/app/rulechain/rulechain.controller.js
  24. 7
      ui/src/app/rulechain/rulechain.directive.js
  25. 82
      ui/src/app/rulechain/rulechain.routes.js
  26. 72
      ui/src/app/rulechain/rulechains.controller.js
  27. 1
      ui/src/app/rulechain/rulenode-fieldset.tpl.html
  28. 1
      ui/src/app/rulechain/rulenode.directive.js
  29. 26
      ui/src/app/services/menu.service.js

9
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -55,6 +55,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -231,13 +232,19 @@ public class RuleChainController extends BaseController {
@ResponseBody
public TextPageData<RuleChain> getRuleChains(
@RequestParam int limit,
@RequestParam(value = "type", required = false) String typeStr,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink));
if (typeStr != null && typeStr.trim().length() > 0) {
RuleChainType type = RuleChainType.valueOf(typeStr);
return checkNotNull(ruleChainService.findTenantRuleChainsByType(tenantId, type, pageLink));
} else {
return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink));
}
} catch (Exception e) {
throw handleException(e);
}

74
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeQueueEntry;
import org.thingsboard.server.common.data.id.AssetId;
@ -44,10 +45,12 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
@ -59,6 +62,7 @@ import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.session.SessionMsgType;
@ -91,6 +95,7 @@ import org.thingsboard.server.gen.edge.UplinkResponseMsg;
import org.thingsboard.server.gen.edge.UserUpdateMsg;
import org.thingsboard.server.service.edge.EdgeContextComponent;
import javax.swing.text.html.parser.Entity;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@ -102,6 +107,9 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import static org.thingsboard.server.gen.edge.UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE;
import static org.thingsboard.server.gen.edge.UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE;
@Slf4j
@Data
public final class EdgeGrpcSession implements Cloneable {
@ -175,7 +183,9 @@ public final class EdgeGrpcSession implements Cloneable {
do {
pageData = ctx.getEdgeService().findQueueEvents(edge.getTenantId(), edge.getId(), pageLink);
if (!pageData.getData().isEmpty()) {
log.trace("[{}] [{}] event(s) are going to be processed.", this.sessionId, pageData.getData().size());
for (Event event : pageData.getData()) {
log.trace("[{}] Processing event [{}]", this.sessionId, event);
EdgeQueueEntry entry;
try {
entry = objectMapper.treeToValue(event.getBody(), EdgeQueueEntry.class);
@ -193,6 +203,9 @@ public final class EdgeGrpcSession implements Cloneable {
processCustomDownlinkMessage(entry);
break;
}
if (ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) {
pushEntityAttributesToEdge(entry);
}
} catch (Exception e) {
log.error("Exception during processing records from queue", e);
}
@ -215,6 +228,63 @@ public final class EdgeGrpcSession implements Cloneable {
}
}
private void pushEntityAttributesToEdge(EdgeQueueEntry entry) throws IOException {
EntityId entityId = null;
String entityName = null;
switch (entry.getEntityType()) {
case EDGE:
entityId = objectMapper.readValue(entry.getData(), Edge.class).getId();
break;
case DEVICE:
entityId = objectMapper.readValue(entry.getData(), Device.class).getId();
break;
case ASSET:
entityId = objectMapper.readValue(entry.getData(), Asset.class).getId();
break;
case ENTITY_VIEW:
entityId = objectMapper.readValue(entry.getData(), EntityView.class).getId();
break;
case DASHBOARD:
entityId = objectMapper.readValue(entry.getData(), Dashboard.class).getId();
break;
}
if (entityId != null) {
ListenableFuture<List<AttributeKvEntry>> ssAttrFuture = ctx.getAttributesService().findAll(edge.getTenantId(), entityId, DataConstants.SERVER_SCOPE);
Futures.transform(ssAttrFuture, ssAttributes -> {
if (ssAttributes != null && !ssAttributes.isEmpty()) {
try {
TbMsgMetaData metaData = new TbMsgMetaData();
ObjectNode entityNode = objectMapper.createObjectNode();
metaData.putValue("scope", DataConstants.SERVER_SCOPE);
for (AttributeKvEntry attr : ssAttributes) {
if (attr.getDataType() == DataType.BOOLEAN) {
entityNode.put(attr.getKey(), attr.getBooleanValue().get());
} else if (attr.getDataType() == DataType.DOUBLE) {
entityNode.put(attr.getKey(), attr.getDoubleValue().get());
} else if (attr.getDataType() == DataType.LONG) {
entityNode.put(attr.getKey(), attr.getLongValue().get());
} else {
entityNode.put(attr.getKey(), attr.getValueAsString());
}
}
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), DataConstants.ATTRIBUTES_UPDATED, entityId, metaData, TbMsgDataType.JSON
, objectMapper.writeValueAsString(entityNode)
, null, null, 0L);
log.debug("Sending donwlink entity data msg, entityName [{}], tbMsg [{}]", entityName, tbMsg);
outputStream.onNext(ResponseMsg.newBuilder()
.setDownlinkMsg(constructDownlinkEntityDataMsg(entityName, tbMsg))
.build());
} catch (Exception e) {
log.error("[{}] Failed to send attribute updates to the edge", edge.getName(), e);
}
}
return null;
});
ListenableFuture<List<AttributeKvEntry>> shAttrFuture = ctx.getAttributesService().findAll(edge.getTenantId(), entityId, DataConstants.SHARED_SCOPE);
ListenableFuture<List<AttributeKvEntry>> clAttrFuture = ctx.getAttributesService().findAll(edge.getTenantId(), entityId, DataConstants.CLIENT_SCOPE);
}
}
private void processCustomDownlinkMessage(EdgeQueueEntry entry) throws IOException {
log.trace("Executing processCustomDownlinkMessage, entry [{}]", entry);
TbMsg tbMsg = objectMapper.readValue(entry.getData(), TbMsg.class);
@ -411,7 +481,7 @@ public final class EdgeGrpcSession implements Cloneable {
return UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE;
case DataConstants.ENTITY_CREATED:
case DataConstants.ENTITY_ASSIGNED_TO_EDGE:
return UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE;
return ENTITY_CREATED_RPC_MESSAGE;
case DataConstants.ENTITY_DELETED:
case DataConstants.ENTITY_UNASSIGNED_FROM_EDGE:
return UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE;
@ -521,6 +591,8 @@ public final class EdgeGrpcSession implements Cloneable {
private RuleNodeProto constructNode(RuleNode node) throws JsonProcessingException {
return RuleNodeProto.newBuilder()
.setIdMSB(node.getId().getId().getMostSignificantBits())
.setIdLSB(node.getId().getId().getLeastSignificantBits())
.setType(node.getType())
.setName(node.getName())
.setDebugMode(node.isDebugMode())

3
common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import java.util.List;
@ -62,6 +63,8 @@ public interface RuleChainService {
TextPageData<RuleChain> findTenantRuleChains(TenantId tenantId, TextPageLink pageLink);
TextPageData<RuleChain> findTenantRuleChainsByType(TenantId tenantId, RuleChainType type, TextPageLink pageLink);
void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId);
void deleteRuleChainsByTenantId(TenantId tenantId);

12
common/edge-api/src/main/proto/edge.proto

@ -125,11 +125,13 @@ message RuleChainMetadataUpdateMsg {
}
message RuleNodeProto {
string type = 1;
string name = 2;
bool debugMode = 3;
string configuration = 4;
string additionalInfo = 5;
int64 idMSB = 1;
int64 idLSB = 2;
string type = 3;
string name = 4;
bool debugMode = 5;
string configuration = 6;
string additionalInfo = 7;
}
message NodeConnectionInfoProto {

8
dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java

@ -113,16 +113,16 @@ public class BaseComponentDescriptorService implements ComponentDescriptorServic
@Override
protected void validateDataImpl(TenantId tenantId, ComponentDescriptor plugin) {
if (plugin.getType() == null) {
throw new DataValidationException("Component type should be specified!.");
throw new DataValidationException("Component type should be specified!");
}
if (plugin.getScope() == null) {
throw new DataValidationException("Component scope should be specified!.");
throw new DataValidationException("Component scope should be specified!");
}
if (StringUtils.isEmpty(plugin.getName())) {
throw new DataValidationException("Component name should be specified!.");
throw new DataValidationException("Component name should be specified!");
}
if (StringUtils.isEmpty(plugin.getClazz())) {
throw new DataValidationException("Component clazz should be specified!.");
throw new DataValidationException("Component clazz should be specified!");
}
}
};

16
dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java

@ -54,7 +54,7 @@ public class BaseEventService implements EventService {
public Optional<Event> saveIfNotExists(Event event) {
eventValidator.validate(event, Event::getTenantId);
if (StringUtils.isEmpty(event.getUid())) {
throw new DataValidationException("Event uid should be specified!.");
throw new DataValidationException("Event uid should be specified!");
}
return eventDao.saveIfNotExists(event);
}
@ -62,16 +62,16 @@ public class BaseEventService implements EventService {
@Override
public Optional<Event> findEvent(TenantId tenantId, EntityId entityId, String eventType, String eventUid) {
if (tenantId == null) {
throw new DataValidationException("Tenant id should be specified!.");
throw new DataValidationException("Tenant id should be specified!");
}
if (entityId == null) {
throw new DataValidationException("Entity id should be specified!.");
throw new DataValidationException("Entity id should be specified!");
}
if (StringUtils.isEmpty(eventType)) {
throw new DataValidationException("Event type should be specified!.");
throw new DataValidationException("Event type should be specified!");
}
if (StringUtils.isEmpty(eventUid)) {
throw new DataValidationException("Event uid should be specified!.");
throw new DataValidationException("Event uid should be specified!");
}
Event event = eventDao.findEvent(tenantId.getId(), entityId, eventType, eventUid);
return event != null ? Optional.of(event) : Optional.empty();
@ -99,13 +99,13 @@ public class BaseEventService implements EventService {
@Override
protected void validateDataImpl(TenantId tenantId, Event event) {
if (event.getEntityId() == null) {
throw new DataValidationException("Entity id should be specified!.");
throw new DataValidationException("Entity id should be specified!");
}
if (StringUtils.isEmpty(event.getType())) {
throw new DataValidationException("Event type should be specified!.");
throw new DataValidationException("Event type should be specified!");
}
if (event.getBody() == null) {
throw new DataValidationException("Event body should be specified!.");
throw new DataValidationException("Event body should be specified!");
}
}
};

1
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -343,6 +343,7 @@ public class ModelConstants {
public static final String RULE_CHAIN_ASSIGNED_EDGES_PROPERTY = "assigned_edges";
public static final String RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "rule_chain_by_tenant_and_search_text";
public static final String RULE_CHAIN_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "rule_chain_by_tenant_by_type_and_search_text";
/**
* Cassandra rule node constants.

12
dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java

@ -106,11 +106,7 @@ public class RuleChainEntity implements SearchTextEntity<RuleChain> {
}
this.tenantId = DaoUtil.getId(ruleChain.getTenantId());
this.name = ruleChain.getName();
if (ruleChain.getType() != null) {
this.type = ruleChain.getType();
} else {
this.type = RuleChainType.SYSTEM;
}
this.type = ruleChain.getType();
this.searchText = ruleChain.getName();
this.firstRuleNodeId = DaoUtil.getId(ruleChain.getFirstRuleNodeId());
this.root = ruleChain.isRoot();
@ -204,11 +200,7 @@ public class RuleChainEntity implements SearchTextEntity<RuleChain> {
ruleChain.setCreatedTime(UUIDs.unixTimestamp(id));
ruleChain.setTenantId(new TenantId(tenantId));
ruleChain.setName(name);
if (type != null) {
ruleChain.setType(type);
} else {
ruleChain.setType(RuleChainType.SYSTEM);
}
ruleChain.setType(type);
if (this.firstRuleNodeId != null) {
ruleChain.setFirstRuleNodeId(new RuleNodeId(this.firstRuleNodeId));
}

12
dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java

@ -103,11 +103,7 @@ public class RuleChainEntity extends BaseSqlEntity<RuleChain> implements SearchT
}
this.tenantId = toString(DaoUtil.getId(ruleChain.getTenantId()));
this.name = ruleChain.getName();
if (ruleChain.getType() != null) {
this.type = ruleChain.getType();
} else {
this.type = RuleChainType.SYSTEM;
}
this.type = ruleChain.getType();
this.searchText = ruleChain.getName();
if (ruleChain.getFirstRuleNodeId() != null) {
this.firstRuleNodeId = UUIDConverter.fromTimeUUID(ruleChain.getFirstRuleNodeId().getId());
@ -141,11 +137,7 @@ public class RuleChainEntity extends BaseSqlEntity<RuleChain> implements SearchT
ruleChain.setCreatedTime(UUIDs.unixTimestamp(getId()));
ruleChain.setTenantId(new TenantId(toUUID(tenantId)));
ruleChain.setName(name);
if (type != null) {
ruleChain.setType(type);
} else {
ruleChain.setType(RuleChainType.SYSTEM);
}
ruleChain.setType(type);
if (firstRuleNodeId != null) {
ruleChain.setFirstRuleNodeId(new RuleNodeId(UUIDConverter.fromString(firstRuleNodeId)));
}

15
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -63,6 +63,8 @@ import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.service.Validator.validateString;
/**
* Created by igor on 3/12/18.
*/
@ -358,6 +360,14 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
return new TextPageData<>(ruleChains, pageLink);
}
@Override
public TextPageData<RuleChain> findTenantRuleChainsByType(TenantId tenantId, RuleChainType type, TextPageLink pageLink) {
Validator.validateId(tenantId, "Incorrect tenant id for search rule chain request.");
Validator.validatePageLink(pageLink, "Incorrect PageLink object for search rule chain request.");
List<RuleChain> ruleChains = ruleChainDao.findRuleChainsByTenantIdAndType(tenantId.getId(), type, pageLink);
return new TextPageData<>(ruleChains, pageLink);
}
@Override
public void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId) {
Validator.validateId(ruleChainId, "Incorrect rule chain id for delete request.");
@ -514,7 +524,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
@Override
protected void validateDataImpl(TenantId tenantId, RuleChain ruleChain) {
if (StringUtils.isEmpty(ruleChain.getName())) {
throw new DataValidationException("Rule chain name should be specified!.");
throw new DataValidationException("Rule chain name should be specified!");
}
if (ruleChain.getType() == null) {
throw new DataValidationException("Rule chain type should be specified!");
}
if (ruleChain.getTenantId() == null || ruleChain.getTenantId().isNullUid()) {
throw new DataValidationException("Rule chain should be assigned to tenant!");

17
dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.nosql.RuleChainEntity;
import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
@ -35,14 +36,19 @@ import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.util.NoSqlDao;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TENANT_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TYPE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_COLUMN_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_TYPE_PROPERTY;
@Component
@Slf4j
@ -73,6 +79,17 @@ public class CassandraRuleChainDao extends CassandraAbstractSearchTextDao<RuleCh
return DaoUtil.convertDataList(ruleChainEntities);
}
@Override
public List<RuleChain> findRuleChainsByTenantIdAndType(UUID tenantId, RuleChainType type, TextPageLink pageLink) {
log.debug("Try to find rule chains by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink);
List<RuleChainEntity> ruleChainEntities = findPageWithTextSearch(new TenantId(tenantId), RULE_CHAIN_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
Arrays.asList(eq(RULE_CHAIN_TYPE_PROPERTY, type),
eq(RULE_CHAIN_TENANT_ID_PROPERTY, tenantId)),
pageLink);
log.trace("Found rule chains [{}] by tenantId [{}] and pageLink [{}]", ruleChainEntities, tenantId, pageLink);
return DaoUtil.convertDataList(ruleChainEntities);
}
@Override
public ListenableFuture<List<RuleChain>> findRuleChainsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) {
log.debug("Try to find rule chains by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);

11
dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.Dao;
import java.util.List;
@ -38,6 +39,16 @@ public interface RuleChainDao extends Dao<RuleChain> {
*/
List<RuleChain> findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink);
/**
* Find rule chains by tenantId, type and page link.
*
* @param tenantId the tenantId
* @param type the type
* @param pageLink the page link
* @return the list of rule chain objects
*/
List<RuleChain> findRuleChainsByTenantIdAndType(UUID tenantId, RuleChainType type, TextPageLink pageLink);
/**
* Find rule chains by tenantId, edgeId and page link.
*

14
dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.RuleChainEntity;
import org.thingsboard.server.dao.relation.RelationDao;
@ -68,6 +69,7 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao<RuleChainEntity, R
@Override
public List<RuleChain> findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) {
log.debug("Try to find rule chains by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
return DaoUtil.convertDataList(ruleChainRepository
.findByTenantId(
UUIDConverter.fromTimeUUID(tenantId),
@ -76,6 +78,18 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao<RuleChainEntity, R
new PageRequest(0, pageLink.getLimit())));
}
@Override
public List<RuleChain> findRuleChainsByTenantIdAndType(UUID tenantId, RuleChainType type, TextPageLink pageLink) {
log.debug("Try to find rule chains by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink);
return DaoUtil.convertDataList(ruleChainRepository
.findByTenantIdAndType(
UUIDConverter.fromTimeUUID(tenantId),
type,
Objects.toString(pageLink.getTextSearch(), ""),
pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()),
new PageRequest(0, pageLink.getLimit())));
}
@Override
public ListenableFuture<List<RuleChain>> findRuleChainsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) {
log.debug("Try to find rule chains by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);

10
dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java

@ -19,6 +19,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.model.sql.RuleChainEntity;
import org.thingsboard.server.dao.util.SqlDao;
@ -35,4 +36,13 @@ public interface RuleChainRepository extends CrudRepository<RuleChainEntity, Str
@Param("idOffset") String idOffset,
Pageable pageable);
@Query("SELECT rc FROM RuleChainEntity rc WHERE rc.tenantId = :tenantId " +
"AND rc.type = :type " +
"AND LOWER(rc.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
"AND rc.id > :idOffset ORDER BY rc.id")
List<RuleChainEntity> findByTenantIdAndType(@Param("tenantId") String tenantId,
@Param("type") RuleChainType type,
@Param("searchText") String searchText,
@Param("idOffset") String idOffset,
Pageable pageable);
}

14
dao/src/main/resources/cassandra/schema-entities.cql

@ -597,22 +597,30 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_chain (
id uuid,
tenant_id uuid,
name text,
type text,
search_text text,
first_rule_node_id uuid,
root boolean,
debug_mode boolean,
configuration text,
additional_info text,
PRIMARY KEY (id, tenant_id)
PRIMARY KEY (id, tenant_id, type)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_search_text AS
SELECT *
from thingsboard.rule_chain
WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( tenant_id, search_text, id )
WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL AND type IS NOT NULL
PRIMARY KEY ( tenant_id, search_text, id, type )
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_by_type_and_search_text AS
SELECT *
from thingsboard.rule_chain
WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL AND type IS NOT NULL
PRIMARY KEY ( tenant_id, type, search_text, id )
WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC );
CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
id uuid,
rule_chain_id uuid,

2
ui/src/app/api/entity.service.js

@ -277,7 +277,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
}
break;
case types.entityType.rulechain:
promise = ruleChainService.getRuleChains(pageLink, config);
promise = ruleChainService.getRuleChains(pageLink, config, subType);
break;
case types.entityType.dashboard:
if (user.authority === 'CUSTOMER_USER') {

5
ui/src/app/api/rule-chain.service.js

@ -46,7 +46,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
return service;
function getRuleChains (pageLink, config) {
function getRuleChains (pageLink, config, type) {
var deferred = $q.defer();
var url = '/api/ruleChains?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@ -58,6 +58,9 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
if (angular.isDefined(type) && type.length) {
url += '&type=' + type;
}
$http.get(url, config).then(function success(response) {
deferred.resolve(prepareRuleChains(response.data));
}, function fail() {

2
ui/src/app/common/types.constant.js

@ -607,7 +607,7 @@ export default angular.module('thingsboard.types', [])
}
},
systemRuleChainType: "SYSTEM",
ruleChainTypes: ["SYSTEM", "EDGE"],
edgeRuleChainType: "EDGE",
ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION", "EXTERNAL"],
ruleChainNodeComponent: {
type: 'RULE_CHAIN',

3
ui/src/app/locale/locale.constant-en_US.json

@ -1391,10 +1391,11 @@
"rulechain": {
"rulechain": "Rule chain",
"rulechains": "Rule chains",
"system-rulechains": "System Rule chains",
"edge-rulechains": "Edge Rule chains",
"root": "Root",
"delete": "Delete rule chain",
"name": "Name",
"type": "Type",
"name-required": "Name is required.",
"description": "Description",
"add": "Add Rule Chain",

2
ui/src/app/rulechain/add-rulechain.tpl.html

@ -31,7 +31,7 @@
<span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<tb-rule-chain rule-chain="vm.item" is-edit="true" rule-chain-scope="'tenant'" the-form="theForm"></tb-rule-chain>
<tb-rule-chain rule-chain="vm.item" is-edit="true" the-form="theForm"></tb-rule-chain>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">

2
ui/src/app/rulechain/add-rulenode.tpl.html

@ -31,7 +31,7 @@
<span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<tb-rule-node rule-node="vm.ruleNode" rule-chain-id="vm.ruleChainId" is-edit="true" the-form="theForm"></tb-rule-node>
<tb-rule-node rule-node="vm.ruleNode" rule-chain-id="vm.ruleChainId" rule-chain-type="vm.ruleChainType" is-edit="true" the-form="theForm"></tb-rule-node>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">

8
ui/src/app/rulechain/rulechain-fieldset.tpl.html

@ -49,14 +49,6 @@
<div translate ng-message="required">rulechain.name-required</div>
</div>
</md-input-container>
<md-input-container class="md-block">
<label translate>rulechain.type</label>
<md-select ng-disabled="$root.loading || !isEdit || ruleChainScope !== 'tenant' || ruleChain.root === true" name="type" ng-model="ruleChain.type">
<md-option ng-repeat="ruleChainType in ruleChainTypes" value="{{ruleChainType}}">
{{ruleChainType}}
</md-option>
</md-select>
</md-input-container>
<md-input-container class="md-block">
<md-checkbox ng-disabled="$root.loading || !isEdit" aria-label="{{ 'rulechain.debug-mode' | translate }}"
ng-model="ruleChain.debugMode">{{ 'rulechain.debug-mode' | translate }}

9
ui/src/app/rulechain/rulechain.controller.js

@ -1179,6 +1179,9 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
function saveRuleChain() {
var saveRuleChainPromise;
if (vm.isImport) {
if (angular.isUndefined(vm.ruleChain.type)) {
vm.ruleChain.type = types.systemRuleChainType;
}
saveRuleChainPromise = ruleChainService.saveRuleChain(vm.ruleChain);
} else {
saveRuleChainPromise = $q.when(vm.ruleChain);
@ -1286,6 +1289,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
ruleNode.configuration = angular.copy(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration);
var ruleChainId = vm.ruleChain.id ? vm.ruleChain.id.id : null;
var ruleChainType = vm.ruleChain.type ? vm.ruleChain.type : types.systemRuleChainType;
vm.enableHotKeys = false;
@ -1294,7 +1298,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
controllerAs: 'vm',
templateUrl: addRuleNodeTemplate,
parent: angular.element($document[0].body),
locals: {ruleNode: ruleNode, ruleChainId: ruleChainId},
locals: {ruleNode: ruleNode, ruleChainId: ruleChainId, ruleChainType: ruleChainType},
fullscreen: true,
targetEvent: $event
}).then(function (ruleNode) {
@ -1365,13 +1369,14 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
}
/*@ngInject*/
export function AddRuleNodeController($scope, $mdDialog, ruleNode, ruleChainId, helpLinks) {
export function AddRuleNodeController($scope, $mdDialog, ruleNode, ruleChainId, ruleChainType, helpLinks) {
var vm = this;
vm.helpLinks = helpLinks;
vm.ruleNode = ruleNode;
vm.ruleChainId = ruleChainId;
vm.ruleChainType = ruleChainType;
vm.add = add;
vm.cancel = cancel;

7
ui/src/app/rulechain/rulechain.directive.js

@ -26,12 +26,6 @@ export default function RuleChainDirective($compile, $templateCache, $mdDialog,
var template = $templateCache.get(ruleChainFieldsetTemplate);
element.html(template);
scope.ruleChainTypes = types.ruleChainTypes;
if (angular.isDefined(scope.ruleChain) && scope.ruleChain != null && angular.isUndefined(scope.ruleChain.type)) {
scope.ruleChain.type = types.systemRuleChainType;
}
scope.onRuleChainIdCopied = function() {
toast.showSuccess($translate.instant('rulechain.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
};
@ -44,7 +38,6 @@ export default function RuleChainDirective($compile, $templateCache, $mdDialog,
scope: {
ruleChain: '=',
isEdit: '=',
ruleChainScope: '=',
isReadOnly: '=',
theForm: '=',
onSetRootRuleChain: '&',

82
ui/src/app/rulechain/rulechain.routes.js

@ -29,6 +29,15 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
$stateProvider
.state('home.ruleChains', {
url: '/ruleChains',
module: 'private',
auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
redirectTo: 'home.ruleChains.system',
ncyBreadcrumb: {
label: '{"icon": "settings_ethernet", "label": "rulechain.rulechains"}'
}
})
.state('home.ruleChains.system', {
url: '/ruleChains/system',
params: {'topIndex': 0},
module: 'private',
auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
@ -41,11 +50,11 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
},
data: {
searchEnabled: true,
pageTitle: 'rulechain.rulechains',
pageTitle: 'rulechain.system-rulechains',
ruleChainsType: 'tenant'
},
ncyBreadcrumb: {
label: '{"icon": "settings_ethernet", "label": "rulechain.rulechains"}'
label: '{"icon": "settings_ethernet", "label": "rulechain.system-rulechains"}'
}
}).state('home.ruleChains.ruleChain', {
url: '/:ruleChainId',
@ -124,8 +133,64 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
ncyBreadcrumb: {
label: '{"icon": "settings_ethernet", "label": "{{ (\'rulechain.import\' | translate) + \': \'+ vm.ruleChain.name }}", "translate": "false"}'
}
})
.state('home.edges.ruleChains', {
}).state('home.ruleChains.edge', {
url: '/ruleChains/edge',
params: {'topIndex': 0},
module: 'private',
auth: ['TENANT_ADMIN'],
views: {
"content@home": {
templateUrl: ruleChainsTemplate,
controllerAs: 'vm',
controller: 'RuleChainsController'
}
},
data: {
searchEnabled: true,
pageTitle: 'rulechain.edge-rulechains',
ruleChainsType: 'edges'
},
ncyBreadcrumb: {
label: '{"icon": "settings_ethernet", "label": "rulechain.edge-rulechains"}'
}
}).state('home.ruleChains.edge.ruleChain', {
url: '/:ruleChainId',
reloadOnSearch: false,
module: 'private',
auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
views: {
"content@home": {
templateUrl: ruleChainTemplate,
controller: 'RuleChainController',
controllerAs: 'vm'
}
},
resolve: {
ruleChain:
/*@ngInject*/
function($stateParams, ruleChainService) {
return ruleChainService.getRuleChain($stateParams.ruleChainId);
},
ruleChainMetaData:
/*@ngInject*/
function($stateParams, ruleChainService) {
return ruleChainService.getRuleChainMetaData($stateParams.ruleChainId);
},
ruleNodeComponents:
/*@ngInject*/
function($stateParams, ruleChainService) {
return ruleChainService.getRuleNodeComponents();
}
},
data: {
import: false,
searchEnabled: false,
pageTitle: 'edge.rulechain'
},
ncyBreadcrumb: {
label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}'
}
}).state('home.edges.ruleChains', {
url: '/:edgeId/ruleChains',
params: {'topIndex': 0},
module: 'private',
@ -138,15 +203,14 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
}
},
data: {
ruleChainsType: 'edge',
searchEnabled: true,
pageTitle: 'edge.rulechains'
pageTitle: 'edge.rulechains',
ruleChainsType: 'edge'
},
ncyBreadcrumb: {
label: '{"icon": "settings_ethernet", "label": "{{ vm.edgeRuleChainsTitle }}", "translate": "false"}'
}
})
.state('home.edges.ruleChains.ruleChain', {
}).state('home.edges.ruleChains.ruleChain', {
url: '/:ruleChainId',
reloadOnSearch: false,
module: 'private',
@ -181,7 +245,7 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
pageTitle: 'edge.rulechain'
},
ncyBreadcrumb: {
label: '{"icon": "settings_ethernet", "label": "edge.rulechain"}'
label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}'
}
});
}

72
ui/src/app/rulechain/rulechains.controller.js

@ -116,7 +116,7 @@ export default function RuleChainsController(ruleChainService, userService, edge
if (vm.ruleChainsScope === 'tenant') {
fetchRuleChainsFunction = function (pageLink) {
return fetchRuleChains(pageLink);
return fetchRuleChains(pageLink, 'SYSTEM');
};
deleteRuleChainFunction = function (ruleChainId) {
return deleteRuleChain(ruleChainId);
@ -132,6 +132,57 @@ export default function RuleChainsController(ruleChainService, userService, edge
isEnabled: isNonRootRuleChain
});
ruleChainActionsList.push({
onAction: function ($event, item) {
vm.grid.deleteItem($event, item);
},
name: function() { return $translate.instant('action.delete') },
details: function() { return $translate.instant('rulechain.delete') },
icon: "delete",
isEnabled: isNonRootRuleChain
});
ruleChainGroupActionsList.push(
{
onAction: function ($event) {
vm.grid.deleteItems($event);
},
name: function() { return $translate.instant('rulechain.delete-rulechains') },
details: deleteRuleChainsActionTitle,
icon: "delete"
}
);
vm.ruleChainGridConfig.addItemActions = [];
vm.ruleChainGridConfig.addItemActions.push({
onAction: function ($event) {
vm.grid.addItem($event);
},
name: function() { return $translate.instant('action.create') },
details: function() { return $translate.instant('rulechain.create-new-rulechain') },
icon: "insert_drive_file"
});
vm.ruleChainGridConfig.addItemActions.push({
onAction: function ($event) {
importExport.importRuleChain($event).then(
function(ruleChainImport) {
$state.go('home.ruleChains.importRuleChain', {ruleChainImport:ruleChainImport});
}
);
},
name: function() { return $translate.instant('action.import') },
details: function() { return $translate.instant('rulechain.import') },
icon: "file_upload"
});
} else if (vm.ruleChainsScope === 'edges') {
fetchRuleChainsFunction = function (pageLink) {
return fetchRuleChains(pageLink, 'EDGE');
};
deleteRuleChainFunction = function (ruleChainId) {
return deleteRuleChain(ruleChainId);
};
ruleChainActionsList.push({
onAction: function ($event, item) {
manageAssignedEdges($event, item);
@ -209,7 +260,6 @@ export default function RuleChainsController(ruleChainService, userService, edge
details: function() { return $translate.instant('rulechain.import') },
icon: "file_upload"
});
} else if (vm.ruleChainsScope === 'edge') {
fetchRuleChainsFunction = function (pageLink) {
return ruleChainService.getEdgeRuleChains(edgeId, pageLink);
@ -291,11 +341,18 @@ export default function RuleChainsController(ruleChainService, userService, edge
vm.grid = grid;
}
function fetchRuleChains(pageLink) {
return ruleChainService.getRuleChains(pageLink);
function fetchRuleChains(pageLink, type) {
return ruleChainService.getRuleChains(pageLink, null, type);
}
function saveRuleChain(ruleChain) {
if (angular.isUndefined(ruleChain.type)) {
if (vm.ruleChainsScope === 'edges') {
ruleChain.type = types.edgeRuleChainType;
} else {
ruleChain.type = types.systemRuleChainType;
}
}
return ruleChainService.saveRuleChain(ruleChain);
}
@ -303,10 +360,11 @@ export default function RuleChainsController(ruleChainService, userService, edge
if ($event) {
$event.stopPropagation();
}
if (vm.ruleChainsScope === 'edge') {
$state.go('home.edges.ruleChains.ruleChain', {
ruleChainId: ruleChain.id.id
});
$state.go('home.edges.ruleChains.ruleChain', {ruleChainId: ruleChain.id.id, edgeId: vm.edge.id.id});
} else if (vm.ruleChainsScope === 'edges') {
$state.go('home.ruleChains.edge.ruleChain', {ruleChainId: ruleChain.id.id});
} else {
$state.go('home.ruleChains.ruleChain', {ruleChainId: ruleChain.id.id});
}

1
ui/src/app/rulechain/rulenode-fieldset.tpl.html

@ -53,6 +53,7 @@
tb-required="true"
exclude-entity-ids="[ruleChainId]"
entity-type="types.entityType.rulechain"
entity-subtype="ruleChainType"
ng-model="params.targetRuleChainId">
</tb-entity-autocomplete>
<md-input-container class="md-block">

1
ui/src/app/rulechain/rulenode.directive.js

@ -70,6 +70,7 @@ export default function RuleNodeDirective($compile, $templateCache, ruleChainSer
link: linker,
scope: {
ruleChainId: '=',
ruleChainType: '=',
ruleNode: '=',
isEdit: '=',
isReadOnly: '=',

26
ui/src/app/services/menu.service.js

@ -156,9 +156,24 @@ function Menu(userService, $state, $rootScope) {
},
{
name: 'rulechain.rulechains',
type: 'link',
type: 'toggle',
state: 'home.ruleChains',
icon: 'settings_ethernet'
height: '80px',
icon: 'settings_ethernet',
pages: [
{
name: 'rulechain.system-rulechains',
type: 'link',
state: 'home.ruleChains.system',
icon: 'settings_ethernet'
},
{
name: 'rulechain.edge-rulechains',
type: 'link',
state: 'home.ruleChains.edge',
icon: 'router'
}
]
},
{
name: 'customer.customers',
@ -214,9 +229,14 @@ function Menu(userService, $state, $rootScope) {
name: 'rulechain.management',
places: [
{
name: 'rulechain.rulechains',
name: 'rulechain.system-rulechains',
icon: 'settings_ethernet',
state: 'home.ruleChains'
},
{
name: 'rulechain.edge-rulechains',
icon: 'router',
state: 'home.edgesRuleChains'
}
]
},

Loading…
Cancel
Save