Browse Source

Merge pull request #5673 from thingsboard/feature/nested-rule-chains

[3.3.3] Nested Rule Chains support
pull/5707/head
Andrew Shvayka 5 years ago
committed by GitHub
parent
commit
0c7c1d0b0c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  2. 6
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
  3. 71
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  4. 41
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainInputMsg.java
  5. 49
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainOutputMsg.java
  6. 18
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
  7. 50
      application/src/main/java/org/thingsboard/server/actors/ruleChain/TbToRuleChainActorMsg.java
  8. 4
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  9. 84
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  10. 2
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  11. 27
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  12. 84
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  13. 190
      application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java
  14. 34
      application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java
  15. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
  16. 7
      common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
  17. 2
      common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java
  18. 1
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java
  19. 45
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainOutputLabelsUsage.java
  20. 44
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainUpdateResult.java
  21. 33
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNodeUpdateResult.java
  22. 10
      common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
  23. 74
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  24. 95
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingCtx.java
  25. 47
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItem.java
  26. 3
      common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeException.java
  27. 19
      common/message/src/main/proto/tbmsg.proto
  28. 17
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  29. 5
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java
  30. 32
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  31. 3
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
  32. 4
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java
  33. 9
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java
  34. 21
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java
  35. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
  36. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java
  37. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java
  38. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java
  39. 4
      dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java
  40. 1
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java
  41. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
  42. 19
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  43. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbAckNode.java
  44. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java
  45. 69
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNode.java
  46. 32
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNodeConfiguration.java
  47. 57
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainOutputNode.java
  48. 93
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  49. 83
      ui-ngx/src/app/core/http/rule-chain.service.ts
  50. 9
      ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts
  51. 8
      ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts
  52. 3
      ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html
  53. 74
      ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts
  54. 2
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-colors.scss
  55. 9
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts
  56. 20
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html
  57. 62
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts
  58. 1
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.html
  59. 3
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.ts
  60. 3
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html
  61. 2
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss
  62. 147
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts
  63. 12
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts
  64. 4
      ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html
  65. 43
      ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts
  66. 3
      ui-ngx/src/app/shared/models/component-descriptor.models.ts
  67. 33
      ui-ngx/src/app/shared/models/rule-chain.models.ts
  68. 23
      ui-ngx/src/app/shared/models/rule-node.models.ts
  69. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

20
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -56,6 +56,7 @@ import org.thingsboard.server.common.data.rule.RuleNodeState;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.TbMsgProcessingStackItem;
import org.thingsboard.server.common.msg.queue.ServiceQueue;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
@ -134,6 +135,25 @@ class DefaultTbContext implements TbContext {
scheduleMsgWithDelay(new RuleNodeToSelfMsg(this, msg), delayMs, nodeCtx.getSelfActor());
}
@Override
public void input(TbMsg msg, RuleChainId ruleChainId) {
msg.pushToStack(nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId());
nodeCtx.getChainActor().tell(new RuleChainInputMsg(ruleChainId, msg));
}
@Override
public void output(TbMsg msg, String relationType) {
TbMsgProcessingStackItem item = msg.popFormStack();
if (item == null) {
ack(msg);
} else {
if (nodeCtx.getSelf().isDebugMode()) {
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType);
}
nodeCtx.getChainActor().tell(new RuleChainOutputMsg(item.getRuleChainId(), item.getRuleNodeId(), relationType, msg));
}
}
@Override
public void enqueue(TbMsg tbMsg, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator());

6
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java

@ -60,6 +60,12 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe
case RULE_CHAIN_TO_RULE_CHAIN_MSG:
processor.onRuleChainToRuleChainMsg((RuleChainToRuleChainMsg) msg);
break;
case RULE_CHAIN_INPUT_MSG:
processor.onRuleChainInputMsg((RuleChainInputMsg) msg);
break;
case RULE_CHAIN_OUTPUT_MSG:
processor.onRuleChainOutputMsg((RuleChainOutputMsg) msg);
break;
case PARTITION_CHANGE_MSG:
processor.onPartitionChangeMsg((PartitionChangeMsg) msg);
break;

71
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java

@ -66,6 +66,7 @@ import java.util.stream.Collectors;
@Slf4j
public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> {
private static final String NA_RELATION_TYPE = "";
private final TbActorRef parent;
private final TbActorRef self;
private final Map<RuleNodeId, RuleNodeCtx> nodeActors;
@ -201,33 +202,58 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
TbMsg msg = envelope.getMsg();
log.trace("[{}][{}] Processing message [{}]: {}", entityId, firstId, msg.getId(), msg);
if (envelope.getRelationTypes() == null || envelope.getRelationTypes().isEmpty()) {
try {
checkActive(envelope.getMsg());
RuleNodeId targetId = msg.getRuleNodeId();
RuleNodeCtx targetCtx;
if (targetId == null) {
targetCtx = firstNode;
msg = msg.copyWithRuleChainId(entityId);
} else {
targetCtx = nodeActors.get(targetId);
}
if (targetCtx != null) {
log.trace("[{}][{}] Pushing message to target rule node", entityId, targetId);
pushMsgToNode(targetCtx, msg, "");
} else {
log.trace("[{}][{}] Rule node does not exist. Probably old message", entityId, targetId);
msg.getCallback().onSuccess();
}
} catch (RuleNodeException rne) {
envelope.getMsg().getCallback().onFailure(rne);
} catch (Exception e) {
envelope.getMsg().getCallback().onFailure(new RuleEngineException(e.getMessage()));
}
onTellNext(msg, true);
} else {
onTellNext(envelope.getMsg(), envelope.getMsg().getRuleNodeId(), envelope.getRelationTypes(), envelope.getFailureMessage());
}
}
private void onTellNext(TbMsg msg, boolean useRuleNodeIdFromMsg) {
try {
checkActive(msg);
RuleNodeId targetId = useRuleNodeIdFromMsg ? msg.getRuleNodeId() : null;
RuleNodeCtx targetCtx;
if (targetId == null) {
targetCtx = firstNode;
msg = msg.copyWithRuleChainId(entityId);
} else {
targetCtx = nodeActors.get(targetId);
}
if (targetCtx != null) {
log.trace("[{}][{}] Pushing message to target rule node", entityId, targetId);
pushMsgToNode(targetCtx, msg, NA_RELATION_TYPE);
} else {
log.trace("[{}][{}] Rule node does not exist. Probably old message", entityId, targetId);
msg.getCallback().onSuccess();
}
} catch (RuleNodeException rne) {
msg.getCallback().onFailure(rne);
} catch (Exception e) {
msg.getCallback().onFailure(new RuleEngineException(e.getMessage()));
}
}
public void onRuleChainInputMsg(RuleChainInputMsg envelope) {
if (entityId.equals(envelope.getRuleChainId())) {
onTellNext(envelope.getMsg(), false);
} else {
parent.tell(envelope);
}
}
public void onRuleChainOutputMsg(RuleChainOutputMsg envelope) {
if (entityId.equals(envelope.getRuleChainId())) {
var originatorNodeId = envelope.getTargetRuleNodeId();
RuleNodeCtx ruleNodeCtx = nodeActors.get(originatorNodeId);
if (ruleNodeCtx != null && ruleNodeCtx.getSelf().isDebugMode()) {
systemContext.persistDebugOutput(tenantId, originatorNodeId, envelope.getMsg(), envelope.getRelationType());
}
onTellNext(envelope.getMsg(), originatorNodeId, Collections.singleton(envelope.getRelationType()), RuleNodeException.UNKNOWN);
} else {
parent.tell(envelope);
}
}
void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) {
try {
checkActive(envelope.getMsg());
@ -356,4 +382,5 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
RuleNode firstRuleNode = firstNode != null ? firstNode.getSelf() : null;
return new RuleNodeException("Rule Chain is not active! Failed to initialize.", ruleChainName, firstRuleNode);
}
}

41
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainInputMsg.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2021 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.actors.ruleChain;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@EqualsAndHashCode(callSuper = true)
@ToString
public final class RuleChainInputMsg extends TbToRuleChainActorMsg {
public RuleChainInputMsg(RuleChainId target, TbMsg tbMsg) {
super(tbMsg, target);
}
@Override
public MsgType getMsgType() {
return MsgType.RULE_CHAIN_INPUT_MSG;
}
}

49
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainOutputMsg.java

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2021 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.actors.ruleChain;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@EqualsAndHashCode(callSuper = true)
@ToString
public final class RuleChainOutputMsg extends TbToRuleChainActorMsg {
@Getter
private final RuleNodeId targetRuleNodeId;
@Getter
private final String relationType;
public RuleChainOutputMsg(RuleChainId target, RuleNodeId targetRuleNodeId, String relationType, TbMsg tbMsg) {
super(tbMsg, target);
this.targetRuleNodeId = targetRuleNodeId;
this.relationType = relationType;
}
@Override
public MsgType getMsgType() {
return MsgType.RULE_CHAIN_OUTPUT_MSG;
}
}

18
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java

@ -31,33 +31,19 @@ import org.thingsboard.server.common.msg.queue.RuleEngineException;
*/
@EqualsAndHashCode(callSuper = true)
@ToString
public final class RuleChainToRuleChainMsg extends TbRuleEngineActorMsg implements RuleChainAwareMsg {
public final class RuleChainToRuleChainMsg extends TbToRuleChainActorMsg {
@Getter
private final RuleChainId target;
@Getter
private final RuleChainId source;
@Getter
private final String fromRelationType;
public RuleChainToRuleChainMsg(RuleChainId target, RuleChainId source, TbMsg tbMsg, String fromRelationType) {
super(tbMsg);
this.target = target;
super(tbMsg, target);
this.source = source;
this.fromRelationType = fromRelationType;
}
@Override
public void onTbActorStopped(TbActorStopReason reason) {
String message = reason == TbActorStopReason.STOPPED ? String.format("Rule chain [%s] stopped", target.getId()) : String.format("Failed to initialize rule chain [%s]!", target.getId());
msg.getCallback().onFailure(new RuleEngineException(message));
}
@Override
public RuleChainId getRuleChainId() {
return target;
}
@Override
public MsgType getMsgType() {
return MsgType.RULE_CHAIN_TO_RULE_CHAIN_MSG;

50
application/src/main/java/org/thingsboard/server/actors/ruleChain/TbToRuleChainActorMsg.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016-2021 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.actors.ruleChain;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbRuleEngineActorMsg;
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
@EqualsAndHashCode(callSuper = true)
@ToString
public abstract class TbToRuleChainActorMsg extends TbRuleEngineActorMsg implements RuleChainAwareMsg {
@Getter
private final RuleChainId target;
public TbToRuleChainActorMsg(TbMsg msg, RuleChainId target) {
super(msg);
this.target = target;
}
@Override
public RuleChainId getRuleChainId() {
return target;
}
@Override
public void onTbActorStopped(TbActorStopReason reason) {
String message = reason == TbActorStopReason.STOPPED ? String.format("Rule chain [%s] stopped", target.getId()) : String.format("Failed to initialize rule chain [%s]!", target.getId());
msg.getCallback().onFailure(new RuleEngineException(message));
}
}

4
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -26,7 +26,9 @@ import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
import org.thingsboard.server.actors.device.DeviceActorCreator;
import org.thingsboard.server.actors.ruleChain.RuleChainInputMsg;
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
import org.thingsboard.server.actors.ruleChain.RuleChainOutputMsg;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.ApiUsageState;
@ -168,6 +170,8 @@ public class TenantActor extends RuleChainManagerActor {
case REMOVE_RPC_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((DeviceAwareMsg) msg, true);
break;
case RULE_CHAIN_INPUT_MSG:
case RULE_CHAIN_OUTPUT_MSG:
case RULE_CHAIN_TO_RULE_CHAIN_MSG:
onRuleChainMsg((RuleChainAwareMsg) msg);
break;

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

@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@ -43,6 +42,7 @@ import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -60,7 +60,9 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainData;
import org.thingsboard.server.common.data.rule.RuleChainImportResult;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
@ -68,15 +70,19 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.install.InstallScripts;
import org.thingsboard.server.service.rule.TbRuleChainService;
import org.thingsboard.server.service.script.JsInvokeService;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -137,6 +143,9 @@ public class RuleChainController extends BaseController {
+ MARKDOWN_CODE_BLOCK_END
+ "\n\n Expected result JSON contains \"output\" and \"error\".";
@Autowired
protected TbRuleChainService tbRuleChainService;
@Autowired
private InstallScripts installScripts;
@ -169,6 +178,44 @@ public class RuleChainController extends BaseController {
}
}
@ApiOperation(value = "Get Rule Chain output labels (getRuleChainOutputLabels)",
notes = "Fetch the unique labels for the \"output\" Rule Nodes that belong to the Rule Chain based on the provided Rule Chain Id. "
+ RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}/output/labels", method = RequestMethod.GET)
@ResponseBody
public Set<String> getRuleChainOutputLabels(
@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION)
@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
checkParameter(RULE_CHAIN_ID, strRuleChainId);
try {
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
checkRuleChain(ruleChainId, Operation.READ);
return tbRuleChainService.getRuleChainOutputLabels(getTenantId(), ruleChainId);
} catch (Exception e) {
throw handleException(e);
}
}
@ApiOperation(value = "Get output labels usage (getRuleChainOutputLabelsUsage)",
notes = "Fetch the list of rule chains and the relation types (labels) they use to process output of the current rule chain based on the provided Rule Chain Id. "
+ RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}/output/labels/usage", method = RequestMethod.GET)
@ResponseBody
public List<RuleChainOutputLabelsUsage> getRuleChainOutputLabelsUsage(
@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION)
@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
checkParameter(RULE_CHAIN_ID, strRuleChainId);
try {
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
checkRuleChain(ruleChainId, Operation.READ);
return tbRuleChainService.getOutputLabelUsage(getCurrentUser().getTenantId(), ruleChainId);
} catch (Exception e) {
throw handleException(e);
}
}
@ApiOperation(value = "Get Rule Chain (getRuleChainById)",
notes = "Fetch the Rule Chain Metadata object based on the provided Rule Chain Id. " + RULE_CHAIN_METADATA_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@ -310,7 +357,10 @@ public class RuleChainController extends BaseController {
@ResponseBody
public RuleChainMetaData saveRuleChainMetaData(
@ApiParam(value = "A JSON value representing the rule chain metadata.")
@RequestBody RuleChainMetaData ruleChainMetaData) throws ThingsboardException {
@RequestBody RuleChainMetaData ruleChainMetaData,
@ApiParam(value = "Update related rule nodes.")
@RequestParam(value = "updateRelated", required = false, defaultValue = "true") boolean updateRelated
) throws ThingsboardException {
try {
TenantId tenantId = getTenantId();
if (debugPerTenantEnabled) {
@ -322,20 +372,36 @@ public class RuleChainController extends BaseController {
}
RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId(), Operation.WRITE);
checkNotNull(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData) ? true : null);
RuleChainUpdateResult result = ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData);
checkNotNull(result.isSuccess() ? true : null);
List<RuleChain> updatedRuleChains;
if (updateRelated && result.isSuccess()) {
updatedRuleChains = tbRuleChainService.updateRelatedRuleChains(tenantId, ruleChainMetaData.getRuleChainId(), result);
} else {
updatedRuleChains = Collections.emptyList();
}
RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId()));
if (RuleChainType.CORE.equals(ruleChain.getType())) {
tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED);
updatedRuleChains.forEach(updatedRuleChain -> {
tbClusterService.broadcastEntityStateChangeEvent(updatedRuleChain.getTenantId(), updatedRuleChain.getId(), ComponentLifecycleEvent.UPDATED);
});
}
logEntityAction(ruleChain.getId(), ruleChain,
null,
ActionType.UPDATED, null, ruleChainMetaData);
logEntityAction(ruleChain.getId(), ruleChain, null, ActionType.UPDATED, null, ruleChainMetaData);
for (RuleChain updatedRuleChain : updatedRuleChains) {
RuleChainMetaData updatedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, updatedRuleChain.getId()));
logEntityAction(updatedRuleChain.getId(), updatedRuleChain, null, ActionType.UPDATED, null, updatedRuleChainMetaData);
}
if (RuleChainType.EDGE.equals(ruleChain.getType())) {
sendEntityNotificationMsg(ruleChain.getTenantId(),
ruleChain.getId(), EdgeEventActionType.UPDATED);
sendEntityNotificationMsg(ruleChain.getTenantId(), ruleChain.getId(), EdgeEventActionType.UPDATED);
updatedRuleChains.forEach(updatedRuleChain -> {
sendEntityNotificationMsg(updatedRuleChain.getTenantId(), updatedRuleChain.getId(), EdgeEventActionType.UPDATED);
});
}
return savedRuleChainMetaData;
@ -681,7 +747,7 @@ public class RuleChainController extends BaseController {
}
@ApiOperation(value = "Get Edge Rule Chains (getEdgeRuleChains)",
notes = "Returns a page of Rule Chains assigned to the specified edge. " + RULE_CHAIN_DESCRIPTION + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
notes = "Returns a page of Rule Chains assigned to the specified edge. " + RULE_CHAIN_DESCRIPTION + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody

2
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -210,10 +210,10 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.3.0 to 3.3.1 ...");
case "3.3.1":
log.info("Upgrading ThingsBoard from version 3.3.1 to 3.3.2 ...");
break;
case "3.3.2":
log.info("Upgrading ThingsBoard from version 3.3.2 to 3.3.3 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.3.2");
dataUpdateService.updateData("3.3.2");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break;

27
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java

@ -152,24 +152,14 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
try {
scannedComponent.setType(type);
Class<?> clazz = Class.forName(clazzName);
switch (type) {
case ENRICHMENT:
case FILTER:
case TRANSFORMATION:
case ACTION:
case EXTERNAL:
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
scannedComponent.setName(ruleNodeAnnotation.name());
scannedComponent.setScope(ruleNodeAnnotation.scope());
NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation);
ObjectNode configurationDescriptor = mapper.createObjectNode();
JsonNode node = mapper.valueToTree(nodeDefinition);
configurationDescriptor.set("nodeDefinition", node);
scannedComponent.setConfigurationDescriptor(configurationDescriptor);
break;
default:
throw new RuntimeException(type + " is not supported yet!");
}
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
scannedComponent.setName(ruleNodeAnnotation.name());
scannedComponent.setScope(ruleNodeAnnotation.scope());
NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation);
ObjectNode configurationDescriptor = mapper.createObjectNode();
JsonNode node = mapper.valueToTree(nodeDefinition);
configurationDescriptor.set("nodeDefinition", node);
scannedComponent.setConfigurationDescriptor(configurationDescriptor);
scannedComponent.setClazz(clazzName);
log.info("Processing scanned component: {}", scannedComponent);
} catch (Exception e) {
@ -200,6 +190,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled());
nodeDefinition.setRelationTypes(getRelationTypesWithFailureRelation(nodeAnnotation));
nodeDefinition.setCustomRelations(nodeAnnotation.customRelations());
nodeDefinition.setRuleChainNode(nodeAnnotation.ruleChainNode());
Class<? extends NodeConfiguration> configClazz = nodeAnnotation.configClazz();
NodeConfiguration config = configClazz.getDeclaredConstructor().newInstance();
NodeConfiguration defaultConfiguration = config.defaultConfiguration();

84
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -25,6 +25,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration;
import org.thingsboard.server.common.data.EntityView;
@ -34,6 +36,8 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
@ -43,8 +47,11 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
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.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmDao;
@ -52,7 +59,9 @@ import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.sql.device.DeviceProfileRepository;
import org.thingsboard.server.dao.tenant.TenantService;
@ -76,6 +85,9 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired
private TenantService tenantService;
@Autowired
private RelationService relationService;
@Autowired
private RuleChainService ruleChainService;
@ -125,6 +137,10 @@ public class DefaultDataUpdateService implements DataUpdateService {
deviceProfileEntityDynamicConditionsUpdater.updateEntities(null);
updateOAuth2Params();
break;
case "3.3.2":
log.info("Updating data from version 3.3.2 to 3.3.3 ...");
nestedRuleNodeUpdater.updateEntities(null);
break;
default:
throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
}
@ -209,6 +225,74 @@ public class DefaultDataUpdateService implements DataUpdateService {
}
};
private final PaginatedUpdater<String, Tenant> nestedRuleNodeUpdater =
new PaginatedUpdater<>() {
@Override
protected String getName() {
return "Tenants nested rule chain updater";
}
@Override
protected boolean forceReportTotal() {
return true;
}
@Override
protected PageData<Tenant> findEntities(String region, PageLink pageLink) {
return tenantService.findTenants(pageLink);
}
@Override
protected void updateEntity(Tenant tenant) {
try {
var tenantId = tenant.getId();
var packSize = 1024;
boolean hasNext = true;
while (hasNext) {
List<EntityRelation> relations = relationService.findRuleNodeToRuleChainRelations(tenantId, RuleChainType.CORE, packSize);
hasNext = relations.size() == packSize;
for (EntityRelation relation : relations) {
RuleNode sourceNode = ruleChainService.findRuleNodeById(tenantId, new RuleNodeId(relation.getFrom().getId()));
RuleChainId sourceRuleChainId = sourceNode.getRuleChainId();
RuleChain targetRuleChain = ruleChainService.findRuleChainById(tenantId, new RuleChainId(relation.getTo().getId()));
RuleNode targetNode = new RuleNode();
targetNode.setName(targetRuleChain.getName());
targetNode.setRuleChainId(sourceRuleChainId);
targetNode.setType(TbRuleChainInputNode.class.getName());
TbRuleChainInputNodeConfiguration configuration = new TbRuleChainInputNodeConfiguration();
configuration.setRuleChainId(targetRuleChain.getId().toString());
targetNode.setConfiguration(JacksonUtil.valueToTree(configuration));
targetNode.setAdditionalInfo(relation.getAdditionalInfo());
targetNode.setDebugMode(false);
targetNode = ruleChainService.saveRuleNode(tenantId, targetNode);
EntityRelation sourceRuleChainToRuleNode = new EntityRelation();
sourceRuleChainToRuleNode.setFrom(sourceRuleChainId);
sourceRuleChainToRuleNode.setTo(targetNode.getId());
sourceRuleChainToRuleNode.setType(EntityRelation.CONTAINS_TYPE);
sourceRuleChainToRuleNode.setTypeGroup(RelationTypeGroup.RULE_CHAIN);
relationService.saveRelation(tenantId, sourceRuleChainToRuleNode);
EntityRelation sourceRuleNodeToTargetRuleNode = new EntityRelation();
sourceRuleNodeToTargetRuleNode.setFrom(sourceNode.getId());
sourceRuleNodeToTargetRuleNode.setTo(targetNode.getId());
sourceRuleNodeToTargetRuleNode.setType(relation.getType());
sourceRuleNodeToTargetRuleNode.setTypeGroup(RelationTypeGroup.RULE_NODE);
sourceRuleNodeToTargetRuleNode.setAdditionalInfo(relation.getAdditionalInfo());
relationService.saveRelation(tenantId, sourceRuleNodeToTargetRuleNode);
//Delete old relation
relationService.deleteRelation(tenantId, relation);
}
}
} catch (Exception e) {
log.error("Unable to update Tenant", e);
}
}
};
private final PaginatedUpdater<String, Tenant> tenantsDefaultEdgeRuleChainUpdater =
new PaginatedUpdater<>() {

190
application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java

@ -0,0 +1,190 @@
/**
* Copyright © 2016-2021 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.service.rule;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.flow.TbRuleChainOutputNode;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
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.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Service
@TbCoreComponent
@Slf4j
public class DefaultTbRuleChainService implements TbRuleChainService {
private final RuleChainService ruleChainService;
private final RelationService relationService;
@Override
public Set<String> getRuleChainOutputLabels(TenantId tenantId, RuleChainId ruleChainId) {
RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(tenantId, ruleChainId);
Set<String> outputLabels = new TreeSet<>();
for (RuleNode ruleNode : metaData.getNodes()) {
if (isOutputRuleNode(ruleNode)) {
outputLabels.add(ruleNode.getName());
}
}
return outputLabels;
}
@Override
public List<RuleChainOutputLabelsUsage> getOutputLabelUsage(TenantId tenantId, RuleChainId ruleChainId) {
List<RuleNode> ruleNodes = ruleChainService.findRuleNodesByTenantIdAndType(tenantId, TbRuleChainInputNode.class.getName(), ruleChainId.getId().toString());
Map<RuleChainId, String> ruleChainNamesCache = new HashMap<>();
// Additional filter, "just in case" the structure of the JSON configuration will change.
var filteredRuleNodes = ruleNodes.stream().filter(node -> {
try {
TbRuleChainInputNodeConfiguration configuration = JacksonUtil.treeToValue(node.getConfiguration(), TbRuleChainInputNodeConfiguration.class);
return ruleChainId.getId().toString().equals(configuration.getRuleChainId());
} catch (Exception e) {
log.warn("[{}][{}] Failed to decode rule node configuration", tenantId, ruleChainId, e);
return false;
}
}).collect(Collectors.toList());
return filteredRuleNodes.stream()
.map(ruleNode -> {
RuleChainOutputLabelsUsage usage = new RuleChainOutputLabelsUsage();
usage.setRuleNodeId(ruleNode.getId());
usage.setRuleNodeName(ruleNode.getName());
usage.setRuleChainId(ruleNode.getRuleChainId());
List<EntityRelation> relations = ruleChainService.getRuleNodeRelations(tenantId, ruleNode.getId());
if (relations != null && !relations.isEmpty()) {
usage.setLabels(relations.stream().map(EntityRelation::getType).collect(Collectors.toSet()));
}
return usage;
})
.filter(usage -> usage.getLabels() != null)
.peek(usage -> {
String ruleChainName = ruleChainNamesCache.computeIfAbsent(usage.getRuleChainId(),
id -> ruleChainService.findRuleChainById(tenantId, id).getName());
usage.setRuleChainName(ruleChainName);
})
.sorted(Comparator
.comparing(RuleChainOutputLabelsUsage::getRuleChainName)
.thenComparing(RuleChainOutputLabelsUsage::getRuleNodeName))
.collect(Collectors.toList());
}
@Override
public List<RuleChain> updateRelatedRuleChains(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateResult result) {
Set<RuleChainId> ruleChainIds = new HashSet<>();
log.debug("[{}][{}] Going to update links in related rule chains", tenantId, ruleChainId);
if (result.getUpdatedRuleNodes() == null || result.getUpdatedRuleNodes().isEmpty()) {
return null;
}
Set<String> oldLabels = new HashSet<>();
Set<String> newLabels = new HashSet<>();
Set<String> confusedLabels = new HashSet<>();
Map<String, String> updatedLabels = new HashMap<>();
for (RuleNodeUpdateResult update : result.getUpdatedRuleNodes()) {
var oldNode = update.getOldRuleNode();
var newNode = update.getNewRuleNode();
if (isOutputRuleNode(newNode)) {
try {
oldLabels.add(oldNode.getName());
newLabels.add(newNode.getName());
if (!oldNode.getName().equals(newNode.getName())) {
String oldLabel = oldNode.getName();
String newLabel = newNode.getName();
if (updatedLabels.containsKey(oldLabel) && !updatedLabels.get(oldLabel).equals(newLabel)) {
confusedLabels.add(oldLabel);
log.warn("[{}][{}] Can't automatically rename the label from [{}] to [{}] due to conflict [{}]", tenantId, ruleChainId, oldLabel, newLabel, updatedLabels.get(oldLabel));
} else {
updatedLabels.put(oldLabel, newLabel);
}
}
} catch (Exception e) {
log.warn("[{}][{}][{}] Failed to decode rule node configuration", tenantId, ruleChainId, newNode.getId(), e);
}
}
}
// Remove all output labels that are renamed to two or more different labels, since we don't which new label to use;
confusedLabels.forEach(updatedLabels::remove);
// Remove all output labels that are renamed but still present in the rule chain;
newLabels.forEach(updatedLabels::remove);
if (!oldLabels.equals(newLabels)) {
ruleChainIds.addAll(updateRelatedRuleChains(tenantId, ruleChainId, updatedLabels));
}
return ruleChainIds.stream().map(id -> ruleChainService.findRuleChainById(tenantId, id)).collect(Collectors.toList());
}
public Set<RuleChainId> updateRelatedRuleChains(TenantId tenantId, RuleChainId ruleChainId, Map<String, String> labelsMap) {
Set<RuleChainId> updatedRuleChains = new HashSet<>();
List<RuleChainOutputLabelsUsage> usageList = getOutputLabelUsage(tenantId, ruleChainId);
for (RuleChainOutputLabelsUsage usage : usageList) {
labelsMap.forEach((oldLabel, newLabel) -> {
if (usage.getLabels().contains(oldLabel)) {
updatedRuleChains.add(usage.getRuleChainId());
renameOutgoingLinks(tenantId, usage.getRuleNodeId(), oldLabel, newLabel);
}
});
}
return updatedRuleChains;
}
private void renameOutgoingLinks(TenantId tenantId, RuleNodeId ruleNodeId, String oldLabel, String newLabel) {
List<EntityRelation> relations = ruleChainService.getRuleNodeRelations(tenantId, ruleNodeId);
for (EntityRelation relation : relations) {
if (relation.getType().equals(oldLabel)) {
relationService.deleteRelation(tenantId, relation);
relation.setType(newLabel);
relationService.saveRelation(tenantId, relation);
}
}
}
private boolean isOutputRuleNode(RuleNode ruleNode) {
return isRuleNode(ruleNode, TbRuleChainOutputNode.class);
}
private boolean isInputRuleNode(RuleNode ruleNode) {
return isRuleNode(ruleNode, TbRuleChainInputNode.class);
}
private boolean isRuleNode(RuleNode ruleNode, Class<?> clazz) {
return ruleNode != null && ruleNode.getType().equals(clazz.getName());
}
}

34
application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2021 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.service.rule;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import java.util.List;
import java.util.Set;
public interface TbRuleChainService {
Set<String> getRuleChainOutputLabels(TenantId tenantId, RuleChainId ruleChainId);
List<RuleChainOutputLabelsUsage> getOutputLabelUsage(TenantId tenantId, RuleChainId ruleChainId);
List<RuleChain> updateRelatedRuleChains(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateResult result);
}

6
common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java

@ -16,12 +16,16 @@
package org.thingsboard.server.dao.relation;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
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.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChainType;
import java.util.List;
@ -78,6 +82,8 @@ public interface RelationService {
void removeRelations(TenantId tenantId, EntityId entityId);
List<EntityRelation> findRuleNodeToRuleChainRelations(TenantId tenantId, RuleChainType ruleChainType, int limit);
// TODO: This method may be useful for some validations in the future
// ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);

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

@ -28,7 +28,9 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainData;
import org.thingsboard.server.common.data.rule.RuleChainImportResult;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode;
import java.util.List;
@ -42,7 +44,7 @@ public interface RuleChainService {
boolean setRootRuleChain(TenantId tenantId, RuleChainId ruleChainId);
boolean saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData);
RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData);
RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId);
@ -88,4 +90,7 @@ public interface RuleChainService {
PageData<RuleChain> findAutoAssignToEdgeRuleChainsByTenantId(TenantId tenantId, PageLink pageLink);
List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String name, String toString);
RuleNode saveRuleNode(TenantId tenantId, RuleNode ruleNode);
}

2
common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java

@ -20,6 +20,6 @@ package org.thingsboard.server.common.data.plugin;
*/
public enum ComponentType {
ENRICHMENT, FILTER, TRANSFORMATION, ACTION, EXTERNAL
ENRICHMENT, FILTER, TRANSFORMATION, ACTION, EXTERNAL, FLOW
}

1
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Created by igor on 3/13/18.

45
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainOutputLabelsUsage.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2021 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.rule;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import java.util.Set;
@ApiModel
@Data
@Slf4j
public class RuleChainOutputLabelsUsage {
@ApiModelProperty(position = 1, required = true, value = "Rule Chain Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private RuleChainId ruleChainId;
@ApiModelProperty(position = 2, required = true, value = "Rule Node Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private RuleNodeId ruleNodeId;
@ApiModelProperty(position = 3, required = true, value = "Rule Chain Name", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String ruleChainName;
@ApiModelProperty(position = 4, required = true, value = "Rule Node Name", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String ruleNodeName;
@ApiModelProperty(position = 5, required = true, value = "Output labels", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private Set<String> labels;
}

44
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainUpdateResult.java

@ -0,0 +1,44 @@
/**
* Copyright © 2016-2021 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.rule;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.id.RuleNodeId;
import java.util.List;
import java.util.Map;
/**
* Created by igor on 3/13/18.
*/
@Data
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class RuleChainUpdateResult {
private final boolean success;
private final List<RuleNodeUpdateResult> updatedRuleNodes;
public static RuleChainUpdateResult failed(){
return new RuleChainUpdateResult(false, null);
}
public static RuleChainUpdateResult successful(List<RuleNodeUpdateResult> updatedRuleNodes){
return new RuleChainUpdateResult(true, updatedRuleNodes);
}
}

33
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNodeUpdateResult.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2021 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.rule;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import org.thingsboard.server.common.data.id.RuleNodeId;
import java.util.Map;
/**
* Created by igor on 3/13/18.
*/
@Data
public class RuleNodeUpdateResult {
private final RuleNode oldRuleNode;
private final RuleNode newRuleNode;
}

10
common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java

@ -61,6 +61,16 @@ public enum MsgType {
*/
RULE_CHAIN_TO_RULE_CHAIN_MSG,
/**
* Message that is sent by RuleNodeActor as input to other RuleChain with command to process TbMsg.
*/
RULE_CHAIN_INPUT_MSG,
/**
* Message that is sent by RuleNodeActor as output to RuleNode in other RuleChain with command to process TbMsg.
*/
RULE_CHAIN_OUTPUT_MSG,
/**
* Message that is sent by RuleActor to RuleChainActor with command to process TbMsg by next nodes in chain.
*/

74
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java

@ -55,24 +55,25 @@ public final class TbMsg implements Serializable {
private final RuleChainId ruleChainId;
private final RuleNodeId ruleNodeId;
@Getter(value = AccessLevel.NONE)
private final AtomicInteger ruleNodeExecCounter;
public int getAndIncrementRuleNodeCounter() {
return ruleNodeExecCounter.getAndIncrement();
}
@JsonIgnore
//This field is not serialized because we use queues and there is no need to do it
private final TbMsgProcessingCtx ctx;
//This field is not serialized because we use queues and there is no need to do it
@JsonIgnore
transient private final TbMsgCallback callback;
public int getAndIncrementRuleNodeCounter() {
return ctx.getAndIncrementRuleNodeCounter();
}
public static TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return newMsg(queueName, type, originator, null, metaData, data, ruleChainId, ruleNodeId);
}
public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId,
metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, 0, TbMsgCallback.EMPTY);
metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY);
}
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) {
@ -80,7 +81,8 @@ public final class TbMsg implements Serializable {
}
public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) {
return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, 0, TbMsgCallback.EMPTY);
return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId,
metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY);
}
// REALLY NEW MSG
@ -90,11 +92,13 @@ public final class TbMsg implements Serializable {
}
public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) {
return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, 0, TbMsgCallback.EMPTY);
return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId,
metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY);
}
public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data) {
return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), dataType, data, null, null, 0, TbMsgCallback.EMPTY);
return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId,
metaData.copy(), dataType, data, null, null, null, TbMsgCallback.EMPTY);
}
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) {
@ -104,46 +108,48 @@ public final class TbMsg implements Serializable {
// For Tests only
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, metaData.copy(), dataType, data, ruleChainId, ruleNodeId, 0, TbMsgCallback.EMPTY);
return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null,
metaData.copy(), dataType, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY);
}
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) {
return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, metaData.copy(), TbMsgDataType.JSON, data, null, null, 0, callback);
return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null,
metaData.copy(), TbMsgDataType.JSON, data, null, null, null, callback);
}
public static TbMsg transformMsg(TbMsg tbMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, type, originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType,
data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ruleNodeExecCounter.get(), tbMsg.callback);
data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.callback);
}
public static TbMsg transformMsg(TbMsg tbMsg, CustomerId customerId) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ruleNodeExecCounter.get(), tbMsg.getCallback());
tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback());
}
public static TbMsg transformMsg(TbMsg tbMsg, RuleChainId ruleChainId) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, ruleChainId, null, tbMsg.ruleNodeExecCounter.get(), tbMsg.getCallback());
tbMsg.data, ruleChainId, null, tbMsg.ctx.copy(), tbMsg.getCallback());
}
public static TbMsg transformMsg(TbMsg tbMsg, String queueName) {
return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, tbMsg.getRuleChainId(), null, tbMsg.ruleNodeExecCounter.get(), tbMsg.getCallback());
tbMsg.data, tbMsg.getRuleChainId(), null, tbMsg.ctx.copy(), tbMsg.getCallback());
}
public static TbMsg transformMsg(TbMsg tbMsg, RuleChainId ruleChainId, String queueName) {
return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, ruleChainId, null, tbMsg.ruleNodeExecCounter.get(), tbMsg.getCallback());
tbMsg.data, ruleChainId, null, tbMsg.ctx.copy(), tbMsg.getCallback());
}
//used for enqueueForTellNext
public static TbMsg newMsg(TbMsg tbMsg, String queueName, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(queueName, UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.customerId, tbMsg.getMetaData().copy(),
tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, tbMsg.ruleNodeExecCounter.get(), TbMsgCallback.EMPTY);
tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, tbMsg.ctx.copy(), TbMsgCallback.EMPTY);
}
private TbMsg(String queueName, UUID id, long ts, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data,
RuleChainId ruleChainId, RuleNodeId ruleNodeId, int ruleNodeExecCounter, TbMsgCallback callback) {
RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgProcessingCtx ctx, TbMsgCallback callback) {
this.id = id;
this.queueName = queueName != null ? queueName : ServiceQueue.MAIN;
if (ts > 0) {
@ -167,7 +173,7 @@ public final class TbMsg implements Serializable {
this.data = data;
this.ruleChainId = ruleChainId;
this.ruleNodeId = ruleNodeId;
this.ruleNodeExecCounter = new AtomicInteger(ruleNodeExecCounter);
this.ctx = ctx != null ? ctx : new TbMsgProcessingCtx();
if (callback != null) {
this.callback = callback;
} else {
@ -209,7 +215,8 @@ public final class TbMsg implements Serializable {
builder.setDataType(msg.getDataType().ordinal());
builder.setData(msg.getData());
builder.setRuleNodeExecCounter(msg.ruleNodeExecCounter.get());
builder.setCtx(msg.ctx.toProto());
return builder.build().toByteArray();
}
@ -231,8 +238,17 @@ public final class TbMsg implements Serializable {
ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB()));
}
TbMsgProcessingCtx ctx;
if (proto.hasCtx()) {
ctx = TbMsgProcessingCtx.fromProto(proto.getCtx());
} else {
// Backward compatibility with unprocessed messages fetched from queue after update.
ctx = new TbMsgProcessingCtx(proto.getRuleNodeExecCounter());
}
TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()];
return new TbMsg(queueName, UUID.fromString(proto.getId()), proto.getTs(), proto.getType(), entityId, customerId, metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, proto.getRuleNodeExecCounter(), callback);
return new TbMsg(queueName, UUID.fromString(proto.getId()), proto.getTs(), proto.getType(), entityId, customerId,
metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, ctx, callback);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException("Could not parse protobuf for TbMsg", e);
}
@ -243,11 +259,13 @@ public final class TbMsg implements Serializable {
}
public TbMsg copyWithRuleChainId(RuleChainId ruleChainId, UUID msgId) {
return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.customerId, this.metaData, this.dataType, this.data, ruleChainId, null, this.ruleNodeExecCounter.get(), callback);
return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.customerId,
this.metaData, this.dataType, this.data, ruleChainId, null, this.ctx, callback);
}
public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId, UUID msgId) {
return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.customerId, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.ruleNodeExecCounter.get(), callback);
return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.customerId,
this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.ctx, callback);
}
public TbMsgCallback getCallback() {
@ -262,4 +280,12 @@ public final class TbMsg implements Serializable {
public String getQueueName() {
return queueName != null ? queueName : ServiceQueue.MAIN;
}
public void pushToStack(RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
ctx.push(ruleChainId, ruleNodeId);
}
public TbMsgProcessingStackItem popFormStack() {
return ctx.pop();
}
}

95
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingCtx.java

@ -0,0 +1,95 @@
/**
* Copyright © 2016-2021 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.msg;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.gen.MsgProtos;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by ashvayka on 13.01.18.
*/
public final class TbMsgProcessingCtx implements Serializable {
private final AtomicInteger ruleNodeExecCounter;
private volatile LinkedList<TbMsgProcessingStackItem> stack;
public TbMsgProcessingCtx() {
this(0);
}
public TbMsgProcessingCtx(int ruleNodeExecCounter) {
this(ruleNodeExecCounter, null);
}
protected TbMsgProcessingCtx(int ruleNodeExecCounter, LinkedList<TbMsgProcessingStackItem> stack) {
this.ruleNodeExecCounter = new AtomicInteger(ruleNodeExecCounter);
this.stack = stack;
}
public int getAndIncrementRuleNodeCounter() {
return ruleNodeExecCounter.getAndIncrement();
}
public TbMsgProcessingCtx copy() {
if (stack == null || stack.isEmpty()) {
return new TbMsgProcessingCtx(ruleNodeExecCounter.get());
} else {
return new TbMsgProcessingCtx(ruleNodeExecCounter.get(), new LinkedList<>(stack));
}
}
public void push(RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
if (stack == null) {
stack = new LinkedList<>();
}
stack.add(new TbMsgProcessingStackItem(ruleChainId, ruleNodeId));
}
public TbMsgProcessingStackItem pop() {
return !stack.isEmpty() ? stack.removeLast() : null;
}
public static TbMsgProcessingCtx fromProto(MsgProtos.TbMsgProcessingCtxProto ctx) {
int ruleNodeExecCounter = ctx.getRuleNodeExecCounter();
if (ctx.getStackCount() > 0) {
LinkedList<TbMsgProcessingStackItem> stack = new LinkedList<>();
for (MsgProtos.TbMsgProcessingStackItemProto item : ctx.getStackList()) {
stack.add(TbMsgProcessingStackItem.fromProto(item));
}
return new TbMsgProcessingCtx(ruleNodeExecCounter, stack);
} else {
return new TbMsgProcessingCtx(ruleNodeExecCounter);
}
}
public MsgProtos.TbMsgProcessingCtxProto toProto() {
var ctxBuilder = MsgProtos.TbMsgProcessingCtxProto.newBuilder();
ctxBuilder.setRuleNodeExecCounter(ruleNodeExecCounter.get());
if (stack != null) {
for (TbMsgProcessingStackItem item : stack) {
ctxBuilder.addStack(item.toProto());
}
}
return ctxBuilder.build();
}
}

47
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItem.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2021 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.msg;
import lombok.Data;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.gen.MsgProtos;
import java.util.UUID;
@Data
public class TbMsgProcessingStackItem {
private final RuleChainId ruleChainId;
private final RuleNodeId ruleNodeId;
MsgProtos.TbMsgProcessingStackItemProto toProto() {
return MsgProtos.TbMsgProcessingStackItemProto.newBuilder()
.setRuleChainIdMSB(ruleChainId.getId().getMostSignificantBits())
.setRuleChainIdLSB(ruleChainId.getId().getLeastSignificantBits())
.setRuleNodeIdMSB(ruleNodeId.getId().getMostSignificantBits())
.setRuleNodeIdLSB(ruleNodeId.getId().getLeastSignificantBits())
.build();
}
static TbMsgProcessingStackItem fromProto(MsgProtos.TbMsgProcessingStackItemProto item){
return new TbMsgProcessingStackItem(
new RuleChainId(new UUID(item.getRuleChainIdMSB(), item.getRuleChainIdLSB())),
new RuleNodeId(new UUID(item.getRuleNodeIdMSB(), item.getRuleNodeIdLSB()))
);
}
}

3
common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeException.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.rule.RuleNode;
public class RuleNodeException extends RuleEngineException {
private static final long serialVersionUID = -1776681087370749776L;
public static final String UNKNOWN = "Unknown";
@Getter
private final String ruleChainName;
@ -45,7 +46,7 @@ public class RuleNodeException extends RuleEngineException {
this.ruleChainId = ruleNode.getRuleChainId();
this.ruleNodeId = ruleNode.getId();
} else {
ruleNodeName = "Unknown";
ruleNodeName = UNKNOWN;
ruleChainId = new RuleChainId(RuleChainId.NULL_UUID);
ruleNodeId = new RuleNodeId(RuleNodeId.NULL_UUID);
}

19
common/message/src/main/proto/tbmsg.proto

@ -19,10 +19,24 @@ package msgqueue;
option java_package = "org.thingsboard.server.common.msg.gen";
option java_outer_classname = "MsgProtos";
// Stores message metadata as map of strings
message TbMsgMetaDataProto {
map<string, string> data = 1;
}
// Stores stack of nested (caller) rule chains
message TbMsgProcessingStackItemProto {
int64 ruleChainIdMSB = 1;
int64 ruleChainIdLSB = 2;
int64 ruleNodeIdMSB = 3;
int64 ruleNodeIdLSB = 4;
}
message TbMsgProcessingCtxProto {
int32 ruleNodeExecCounter = 1;
repeated TbMsgProcessingStackItemProto stack = 2;
}
message TbMsgProto {
string id = 1;
string type = 2;
@ -39,14 +53,17 @@ message TbMsgProto {
TbMsgMetaDataProto metaData = 11;
//Transaction Data (12) was removed in 2.5
// Transaction Data (12) was removed in 2.5
int32 dataType = 13;
string data = 14;
int64 ts = 15;
// Will be removed in 3.4. Moved to processing context
int32 ruleNodeExecCounter = 16;
int64 customerIdMSB = 17;
int64 customerIdLSB = 18;
TbMsgProcessingCtxProto ctx = 19;
}

17
dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java

@ -30,8 +30,11 @@ import org.springframework.cache.annotation.Caching;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
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.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
@ -39,6 +42,7 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.ConstraintValidator;
@ -54,6 +58,7 @@ import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
import static org.thingsboard.server.common.data.CacheConstants.RELATIONS_CACHE;
import static org.thingsboard.server.dao.service.Validator.validateId;
/**
* Created by ashvayka on 28.04.17.
@ -194,11 +199,11 @@ public class BaseRelationService implements RelationService {
outboundRelations.addAll(relationDao.findAllByFrom(tenantId, entityId, typeGroup));
}
for (EntityRelation relation : inboundRelations){
for (EntityRelation relation : inboundRelations) {
delete(tenantId, cache, relation, true);
}
for (EntityRelation relation : outboundRelations){
for (EntityRelation relation : outboundRelations) {
delete(tenantId, cache, relation, false);
}
@ -556,6 +561,14 @@ public class BaseRelationService implements RelationService {
}
}
@Override
public List<EntityRelation> findRuleNodeToRuleChainRelations(TenantId tenantId, RuleChainType ruleChainType, int limit) {
log.trace("Executing findRuleNodeToRuleChainRelations, tenantId [{}] and limit {}",
tenantId, limit);
validateId(tenantId, "Invalid tenant id: " + tenantId);
return relationDao.findRuleNodeToRuleChainRelations(tenantId, ruleChainType, limit);
}
protected void validate(EntityRelation relation) {
if (relation == null) {
throw new DataValidationException("Relation type should be specified!");

5
dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java

@ -16,13 +16,11 @@
package org.thingsboard.server.dao.relation;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
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.RuleChainType;
import java.util.List;
@ -63,4 +61,5 @@ public interface RelationDao {
ListenableFuture<Boolean> deleteOutboundRelationsAsync(TenantId tenantId, EntityId entity);
List<EntityRelation> findRuleNodeToRuleChainRelations(TenantId tenantId, RuleChainType ruleChainType, int limit);
}

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

@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
@ -47,8 +48,11 @@ import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChainData;
import org.thingsboard.server.common.data.rule.RuleChainImportResult;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
@ -72,6 +76,7 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.TENANT;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateString;
/**
* Created by igor on 3/12/18.
@ -130,13 +135,14 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
@Override
@Transactional
public boolean saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData) {
public RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData) {
Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id.");
RuleChain ruleChain = findRuleChainById(tenantId, ruleChainMetaData.getRuleChainId());
if (ruleChain == null) {
return false;
return RuleChainUpdateResult.failed();
}
ConstraintValidator.validateFields(ruleChainMetaData);
List<RuleNodeUpdateResult> updatedRuleNodes = new ArrayList<>();
if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) {
validateCircles(ruleChainMetaData.getConnections());
@ -161,11 +167,15 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
for (RuleNode existingNode : existingRuleNodes) {
deleteEntityRelations(tenantId, existingNode.getId());
Integer index = ruleNodeIndexMap.get(existingNode.getId());
RuleNode newRuleNode = null;
if (index != null) {
toAddOrUpdate.add(ruleChainMetaData.getNodes().get(index));
newRuleNode = ruleChainMetaData.getNodes().get(index);
toAddOrUpdate.add(newRuleNode);
} else {
updatedRuleNodes.add(new RuleNodeUpdateResult(existingNode, null));
toDelete.add(existingNode);
}
updatedRuleNodes.add(new RuleNodeUpdateResult(existingNode, newRuleNode));
}
if (nodes != null) {
for (RuleNode node : toAddOrUpdate) {
@ -209,7 +219,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
}
}
return true;
return RuleChainUpdateResult.successful(updatedRuleNodes);
}
private void validateCircles(List<NodeConnectionInfo> connectionInfos) {
@ -652,6 +662,20 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
return ruleChainDao.findAutoAssignToEdgeRuleChainsByTenantId(tenantId.getId(), pageLink);
}
@Override
public List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String search) {
log.trace("Executing findRuleNodes, tenantId [{}], type {}, search {}", tenantId, type, search);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateString(type, "Incorrect type of the rule node");
validateString(search, "Incorrect search text");
return ruleNodeDao.findRuleNodesByTenantIdAndType(tenantId, type, search);
}
@Override
public RuleNode saveRuleNode(TenantId tenantId, RuleNode ruleNode) {
return ruleNodeDao.save(tenantId, ruleNode);
}
private void checkRuleNodesAndDelete(TenantId tenantId, RuleChainId ruleChainId) {
try {
ruleChainDao.removeById(tenantId, ruleChainId.getId());

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

@ -19,11 +19,13 @@ 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.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.TenantEntityDao;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
/**
@ -79,4 +81,5 @@ public interface RuleChainDao extends Dao<RuleChain>, TenantEntityDao {
Collection<RuleChain> findByTenantIdAndTypeAndName(TenantId tenantId, RuleChainType type, String name);
List<RuleChainOutputLabelsUsage> getOutputLabelUsage(UUID id, UUID id1);
}

4
dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java

@ -15,12 +15,16 @@
*/
package org.thingsboard.server.dao.rule;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.Dao;
import java.util.List;
/**
* Created by igor on 3/12/18.
*/
public interface RuleNodeDao extends Dao<RuleNode> {
List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String search);
}

9
dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.RelationCompositeKey;
import org.thingsboard.server.dao.model.sql.RelationEntity;
@ -196,11 +198,16 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
});
}
@Override
public List<EntityRelation> findRuleNodeToRuleChainRelations(TenantId tenantId, RuleChainType ruleChainType, int limit) {
return DaoUtil.convertDataList(relationRepository.findRuleNodeToRuleChainRelations(tenantId.getId(), ruleChainType, PageRequest.of(0, limit)));
}
private Specification<RelationEntity> getEntityFieldsSpec(EntityId from, String relationType, RelationTypeGroup typeGroup, EntityType childType) {
return (root, criteriaQuery, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (from != null) {
Predicate fromIdPredicate = criteriaBuilder.equal(root.get("fromId"), from.getId());
Predicate fromIdPredicate = criteriaBuilder.equal(root.get("fromId"), from.getId());
predicates.add(fromIdPredicate);
Predicate fromEntityTypePredicate = criteriaBuilder.equal(root.get("fromType"), from.getEntityType().name());
predicates.add(fromEntityTypePredicate);

21
dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java

@ -15,11 +15,20 @@
*/
package org.thingsboard.server.dao.sql.relation;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.model.sql.RelationCompositeKey;
import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.model.sql.RuleChainEntity;
import java.util.List;
import java.util.UUID;
@ -48,6 +57,17 @@ public interface RelationRepository
List<RelationEntity> findAllByFromIdAndFromType(UUID fromId,
String fromType);
@Query("SELECT r FROM RelationEntity r WHERE " +
"r.fromId in (SELECT id from RuleNodeEntity where ruleChainId in " +
"(SELECT id from RuleChainEntity where tenantId = :tenantId and type = :ruleChainType ))" +
"AND r.fromType = 'RULE_NODE' " +
"AND r.toType = 'RULE_CHAIN' " +
"AND r.relationTypeGroup = 'RULE_NODE'")
List<RelationEntity> findRuleNodeToRuleChainRelations(
@Param("tenantId") UUID tenantId,
@Param("ruleChainType") RuleChainType ruleChainType,
Pageable page);
@Transactional
<S extends RelationEntity> S save(S entity);
@ -56,4 +76,5 @@ public interface RelationRepository
@Transactional
void deleteByFromIdAndFromType(UUID fromId, String fromType);
}

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

@ -23,6 +23,7 @@ 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.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.RuleChainEntity;
@ -30,6 +31,7 @@ import org.thingsboard.server.dao.rule.RuleChainDao;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@ -103,6 +105,11 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao<RuleChainEntity, R
return DaoUtil.convertDataList(ruleChainRepository.findByTenantIdAndTypeAndName(tenantId.getId(), type, name));
}
@Override
public List<RuleChainOutputLabelsUsage> getOutputLabelUsage(UUID tenantId, UUID ruleChainId) {
return null;
}
@Override
public Long countByTenantId(TenantId tenantId) {
return ruleChainRepository.countByTenantId(tenantId.getId());

7
dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java

@ -19,11 +19,14 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.RuleNodeEntity;
import org.thingsboard.server.dao.rule.RuleNodeDao;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import java.util.List;
import java.util.UUID;
@Slf4j
@ -43,4 +46,8 @@ public class JpaRuleNodeDao extends JpaAbstractSearchTextDao<RuleNodeEntity, Rul
return ruleNodeRepository;
}
@Override
public List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String search) {
return DaoUtil.convertDataList(ruleNodeRepository.findRuleNodesByTenantIdAndType(tenantId.getId(), type, search));
}
}

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

@ -20,7 +20,11 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.model.sql.RuleChainEntity;
import java.util.List;
@ -59,6 +63,7 @@ public interface RuleChainRepository extends PagingAndSortingRepository<RuleChai
@Param("searchText") String searchText,
Pageable pageable);
RuleChainEntity findByTenantIdAndTypeAndRootIsTrue(UUID tenantId, RuleChainType ruleChainType);
Long countByTenantId(UUID tenantId);

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

@ -15,11 +15,21 @@
*/
package org.thingsboard.server.dao.sql.rule;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.model.sql.RuleNodeEntity;
import java.util.List;
import java.util.UUID;
public interface RuleNodeRepository extends CrudRepository<RuleNodeEntity, UUID> {
@Query("SELECT r FROM RuleNodeEntity r WHERE r.ruleChainId in " +
"(select id from RuleChainEntity rc WHERE rc.tenantId = :tenantId) " +
"AND r.type = :ruleType AND LOWER(r.configuration) LIKE LOWER(CONCAT('%', :searchText, '%')) ")
List<RuleNodeEntity> findRuleNodesByTenantIdAndType(@Param("tenantId") UUID tenantId,
@Param("ruleType") String ruleType,
@Param("searchText") String searchText);
}

4
dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java

@ -293,7 +293,7 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest {
ruleNodes.set(name3Index, ruleNode4);
Assert.assertTrue(ruleChainService.saveRuleChainMetaData(tenantId, savedRuleChainMetaData));
Assert.assertTrue(ruleChainService.saveRuleChainMetaData(tenantId, savedRuleChainMetaData).isSuccess());
RuleChainMetaData updatedRuleChainMetaData = ruleChainService.loadRuleChainMetaData(tenantId, savedRuleChainMetaData.getRuleChainId());
Assert.assertEquals(3, updatedRuleChainMetaData.getNodes().size());
@ -406,7 +406,7 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest {
ruleChainMetaData.addConnectionInfo(0,2,"fail");
ruleChainMetaData.addConnectionInfo(1,2,"success");
Assert.assertTrue(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData));
Assert.assertTrue(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData).isSuccess());
return ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId());
}

1
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java

@ -27,6 +27,7 @@ public class NodeDefinition {
private boolean outEnabled;
String[] relationTypes;
boolean customRelations;
boolean ruleChainNode;
JsonNode defaultConfiguration;
String[] uiResources;
String configDirective;

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java

@ -58,6 +58,8 @@ public @interface RuleNode {
boolean customRelations() default false;
boolean ruleChainNode() default false;
RuleChainType[] ruleChainTypes() default {RuleChainType.CORE, RuleChainType.EDGE};
}

19
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java

@ -29,6 +29,7 @@ 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.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -121,6 +122,24 @@ public interface TbContext {
*/
void enqueue(TbMsg msg, Runnable onSuccess, Consumer<Throwable> onFailure);
/**
* Sends message to the nested rule chain.
* Fails processing of the message if the nested rule chain is not found.
*
* @param msg - the message
* @param ruleChainId - the id of a nested rule chain
*/
void input(TbMsg msg, RuleChainId ruleChainId);
/**
* Sends message to the caller rule chain.
* Acknowledge the message if no caller rule chain is present in processing stack
*
* @param msg - the message
* @param relationType - the relation type that will be used to route messages in the caller rule chain
*/
void output(TbMsg msg, String relationType);
/**
* Puts new message to custom queue for processing
*

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbAckNode.java

@ -28,7 +28,7 @@ import org.thingsboard.server.common.msg.TbMsg;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
type = ComponentType.FLOW,
name = "acknowledge",
configClazz = EmptyNodeConfiguration.class,
nodeDescription = "Acknowledges the incoming message",

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java

@ -31,7 +31,7 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
type = ComponentType.FLOW,
name = "checkpoint",
configClazz = TbCheckpointNodeConfiguration.class,
nodeDescription = "transfers the message to another queue",

69
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNode.java

@ -0,0 +1,69 @@
/**
* Copyright © 2016-2021 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.rule.engine.flow;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.UUID;
@Slf4j
@RuleNode(
type = ComponentType.FLOW,
name = "rule chain",
configClazz = TbRuleChainInputNodeConfiguration.class,
nodeDescription = "transfers the message to another rule chain",
nodeDetails = "Allows to nest the rule chain similar to single rule node. " +
"The incoming message is forwarded to the input node of the specified target rule chain. " +
"The target rule chain may produce multiple labeled outputs. " +
"You may use the outputs to forward the results of processing to other rule nodes.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbFlowNodeRuleChainInputConfig",
relationTypes = {},
ruleChainNode = true,
customRelations = true
)
public class TbRuleChainInputNode implements TbNode {
private TbRuleChainInputNodeConfiguration config;
private RuleChainId ruleChainId;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbRuleChainInputNodeConfiguration.class);
this.ruleChainId = new RuleChainId(UUID.fromString(config.getRuleChainId()));
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) {
ctx.input(msg, ruleChainId);
}
@Override
public void destroy() {
}
}

32
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNodeConfiguration.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2021 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.rule.engine.flow;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.id.RuleChainId;
@Data
public class TbRuleChainInputNodeConfiguration implements NodeConfiguration<TbRuleChainInputNodeConfiguration> {
private String ruleChainId;
@Override
public TbRuleChainInputNodeConfiguration defaultConfiguration() {
return new TbRuleChainInputNodeConfiguration();
}
}

57
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainOutputNode.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2021 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.rule.engine.flow;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.EmptyNodeConfiguration;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
@Slf4j
@RuleNode(
type = ComponentType.FLOW,
name = "output",
configClazz = EmptyNodeConfiguration.class,
nodeDescription = "transfers the message to the caller rule chain",
nodeDetails = "Produces output of the rule chain processing. " +
"The output is forwarded to the caller rule chain, as an output of the corresponding \"input\" rule node. " +
"The output rule node name corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain. ",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbFlowNodeRuleChainOutputConfig",
outEnabled = false
)
public class TbRuleChainOutputNode implements TbNode {
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) {
ctx.output(msg, ctx.getSelf().getName());
}
@Override
public void destroy() {
}
}

93
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

@ -3691,6 +3691,89 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImpo
}]
}] });
class RuleChainInputComponent extends RuleNodeConfigurationComponent {
constructor(store, fb) {
super(store);
this.store = store;
this.fb = fb;
this.entityType = EntityType;
}
configForm() {
return this.ruleChainInputConfigForm;
}
onConfigurationSet(configuration) {
this.ruleChainInputConfigForm = this.fb.group({
ruleChainId: [configuration ? configuration.ruleChainId : null, [Validators.required]]
});
}
}
RuleChainInputComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleChainInputComponent, deps: [{ token: i1.Store }, { token: i2.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
RuleChainInputComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.14", type: RuleChainInputComponent, selector: "tb-flow-node-rule-chain-input-config", usesInheritance: true, ngImport: i0, template: "<section [formGroup]=\"ruleChainInputConfigForm\" fxLayout=\"column\">\n <tb-entity-autocomplete required\n [excludeEntityIds]=\"[ruleChainId]\"\n [entityType]=\"entityType.RULE_CHAIN\"\n [entitySubtype]=\"ruleChainType\"\n formControlName=\"ruleChainId\">\n </tb-entity-autocomplete>\n</section>\n", components: [{ type: i8$3.EntityAutocompleteComponent, selector: "tb-entity-autocomplete", inputs: ["entityType", "entitySubtype", "excludeEntityIds", "labelText", "requiredText", "required", "disabled"], outputs: ["entityChanged"] }], directives: [{ type: i8.DefaultLayoutDirective, selector: " [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]", inputs: ["fxLayout", "fxLayout.xs", "fxLayout.sm", "fxLayout.md", "fxLayout.lg", "fxLayout.xl", "fxLayout.lt-sm", "fxLayout.lt-md", "fxLayout.lt-lg", "fxLayout.lt-xl", "fxLayout.gt-xs", "fxLayout.gt-sm", "fxLayout.gt-md", "fxLayout.gt-lg"] }, { type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { type: i2.FormControlName, selector: "[formControlName]", inputs: ["disabled", "formControlName", "ngModel"], outputs: ["ngModelChange"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleChainInputComponent, decorators: [{
type: Component,
args: [{
selector: 'tb-flow-node-rule-chain-input-config',
templateUrl: './rule-chain-input.component.html',
styleUrls: []
}]
}], ctorParameters: function () { return [{ type: i1.Store }, { type: i2.FormBuilder }]; } });
class RuleChainOutputComponent extends RuleNodeConfigurationComponent {
constructor(store, fb) {
super(store);
this.store = store;
this.fb = fb;
}
configForm() {
return this.ruleChainOutputConfigForm;
}
onConfigurationSet(configuration) {
this.ruleChainOutputConfigForm = this.fb.group({});
}
}
RuleChainOutputComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleChainOutputComponent, deps: [{ token: i1.Store }, { token: i2.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
RuleChainOutputComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.14", type: RuleChainOutputComponent, selector: "tb-flow-node-rule-chain-output-config", usesInheritance: true, ngImport: i0, template: "<section [formGroup]=\"ruleChainOutputConfigForm\" fxLayout=\"column\">\n <div innerHTML=\"{{ 'tb.rulenode.output-node-name-hint' | translate }}\"></div>\n</section>\n", directives: [{ type: i8.DefaultLayoutDirective, selector: " [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]", inputs: ["fxLayout", "fxLayout.xs", "fxLayout.sm", "fxLayout.md", "fxLayout.lg", "fxLayout.xl", "fxLayout.lt-sm", "fxLayout.lt-md", "fxLayout.lt-lg", "fxLayout.lt-xl", "fxLayout.gt-xs", "fxLayout.gt-sm", "fxLayout.gt-md", "fxLayout.gt-lg"] }, { type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }], pipes: { "translate": i4.TranslatePipe } });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleChainOutputComponent, decorators: [{
type: Component,
args: [{
selector: 'tb-flow-node-rule-chain-output-config',
templateUrl: './rule-chain-output.component.html',
styleUrls: []
}]
}], ctorParameters: function () { return [{ type: i1.Store }, { type: i2.FormBuilder }]; } });
class RuleNodeCoreConfigFlowModule {
}
RuleNodeCoreConfigFlowModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigFlowModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
RuleNodeCoreConfigFlowModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigFlowModule, declarations: [RuleChainInputComponent,
RuleChainOutputComponent], imports: [CommonModule,
SharedModule,
RulenodeCoreConfigCommonModule], exports: [RuleChainInputComponent,
RuleChainOutputComponent] });
RuleNodeCoreConfigFlowModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigFlowModule, imports: [[
CommonModule,
SharedModule,
RulenodeCoreConfigCommonModule
]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigFlowModule, decorators: [{
type: NgModule,
args: [{
declarations: [
RuleChainInputComponent,
RuleChainOutputComponent
],
imports: [
CommonModule,
SharedModule,
RulenodeCoreConfigCommonModule
],
exports: [
RuleChainInputComponent,
RuleChainOutputComponent
]
}]
}] });
function addRuleNodeCoreLocaleEnglish(translate) {
const enUS = {
tb: {
@ -4105,7 +4188,8 @@ function addRuleNodeCoreLocaleEnglish(translate) {
'for value from message body',
'alarm-severity-pattern-hint': 'Hint: use <code><span style="color: #000;">$&#123;</span>metadataKey<span style="color: #000;">&#125;</span></code> ' +
'for value from metadata, <code><span style="color: #000;">$[</span>messageKey<span style="color: #000;">]</span></code> ' +
'for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)'
'for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)',
'output-node-name-hint': 'The <b>rule node name</b> corresponds to the <b>relation type</b> of the output message, and it is used to forward messages to other rule nodes in the caller rule chain.'
},
'key-val': {
key: 'Key',
@ -4134,6 +4218,7 @@ RuleNodeCoreConfigModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0"
RuleNodeCoreConfigFilterModule,
RulenodeCoreConfigEnrichmentModule,
RulenodeCoreConfigTransformModule,
RuleNodeCoreConfigFlowModule,
EmptyConfigComponent] });
RuleNodeCoreConfigModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigModule, imports: [[
CommonModule,
@ -4141,7 +4226,8 @@ RuleNodeCoreConfigModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0"
], RuleNodeCoreConfigActionModule,
RuleNodeCoreConfigFilterModule,
RulenodeCoreConfigEnrichmentModule,
RulenodeCoreConfigTransformModule] });
RulenodeCoreConfigTransformModule,
RuleNodeCoreConfigFlowModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImport: i0, type: RuleNodeCoreConfigModule, decorators: [{
type: NgModule,
args: [{
@ -4157,6 +4243,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImpo
RuleNodeCoreConfigFilterModule,
RulenodeCoreConfigEnrichmentModule,
RulenodeCoreConfigTransformModule,
RuleNodeCoreConfigFlowModule,
EmptyConfigComponent
]
}]
@ -4170,5 +4257,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.14", ngImpo
* Generated bundle index. Do not edit.
*/
export { AssignCustomerConfigComponent, AttributesConfigComponent, AzureIotHubConfigComponent, CalculateDeltaConfigComponent, ChangeOriginatorConfigComponent, CheckAlarmStatusComponent, CheckMessageConfigComponent, CheckPointConfigComponent, CheckRelationConfigComponent, ClearAlarmConfigComponent, CreateAlarmConfigComponent, CreateRelationConfigComponent, CredentialsConfigComponent, CustomerAttributesConfigComponent, DeleteRelationConfigComponent, DeviceAttributesConfigComponent, DeviceProfileConfigComponent, DeviceRelationsQueryConfigComponent, EmptyConfigComponent, EntityDetailsConfigComponent, GeneratorConfigComponent, GetTelemetryFromDatabaseConfigComponent, GpsGeoActionConfigComponent, GpsGeoFilterConfigComponent, KafkaConfigComponent, KvMapConfigComponent, LogConfigComponent, MessageTypeConfigComponent, MessageTypesConfigComponent, MqttConfigComponent, MsgCountConfigComponent, MsgDelayConfigComponent, OriginatorAttributesConfigComponent, OriginatorFieldsConfigComponent, OriginatorTypeConfigComponent, PubSubConfigComponent, PushToCloudConfigComponent, PushToEdgeConfigComponent, RabbitMqConfigComponent, RelatedAttributesConfigComponent, RelationsQueryConfigComponent, RestApiCallConfigComponent, RpcReplyConfigComponent, RpcRequestConfigComponent, RuleNodeCoreConfigActionModule, RuleNodeCoreConfigFilterModule, RuleNodeCoreConfigModule, RulenodeCoreConfigCommonModule, RulenodeCoreConfigEnrichmentModule, RulenodeCoreConfigTransformModule, SafeHtmlPipe, SaveToCustomTableConfigComponent, ScriptConfigComponent, SendEmailConfigComponent, SendSmsConfigComponent, SnsConfigComponent, SqsConfigComponent, SwitchConfigComponent, TenantAttributesConfigComponent, TimeseriesConfigComponent, ToEmailConfigComponent, TransformScriptConfigComponent, UnassignCustomerConfigComponent };
export { AssignCustomerConfigComponent, AttributesConfigComponent, AzureIotHubConfigComponent, CalculateDeltaConfigComponent, ChangeOriginatorConfigComponent, CheckAlarmStatusComponent, CheckMessageConfigComponent, CheckPointConfigComponent, CheckRelationConfigComponent, ClearAlarmConfigComponent, CreateAlarmConfigComponent, CreateRelationConfigComponent, CredentialsConfigComponent, CustomerAttributesConfigComponent, DeleteRelationConfigComponent, DeviceAttributesConfigComponent, DeviceProfileConfigComponent, DeviceRelationsQueryConfigComponent, EmptyConfigComponent, EntityDetailsConfigComponent, GeneratorConfigComponent, GetTelemetryFromDatabaseConfigComponent, GpsGeoActionConfigComponent, GpsGeoFilterConfigComponent, KafkaConfigComponent, KvMapConfigComponent, LogConfigComponent, MessageTypeConfigComponent, MessageTypesConfigComponent, MqttConfigComponent, MsgCountConfigComponent, MsgDelayConfigComponent, OriginatorAttributesConfigComponent, OriginatorFieldsConfigComponent, OriginatorTypeConfigComponent, PubSubConfigComponent, PushToCloudConfigComponent, PushToEdgeConfigComponent, RabbitMqConfigComponent, RelatedAttributesConfigComponent, RelationsQueryConfigComponent, RestApiCallConfigComponent, RpcReplyConfigComponent, RpcRequestConfigComponent, RuleChainInputComponent, RuleChainOutputComponent, RuleNodeCoreConfigActionModule, RuleNodeCoreConfigFilterModule, RuleNodeCoreConfigFlowModule, RuleNodeCoreConfigModule, RulenodeCoreConfigCommonModule, RulenodeCoreConfigEnrichmentModule, RulenodeCoreConfigTransformModule, SafeHtmlPipe, SaveToCustomTableConfigComponent, ScriptConfigComponent, SendEmailConfigComponent, SendSmsConfigComponent, SnsConfigComponent, SqsConfigComponent, SwitchConfigComponent, TenantAttributesConfigComponent, TimeseriesConfigComponent, ToEmailConfigComponent, TransformScriptConfigComponent, UnassignCustomerConfigComponent };
//# sourceMappingURL=rulenode-core-config.js.map

83
ui-ngx/src/app/core/http/rule-chain.service.ts

@ -21,11 +21,8 @@ import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
import {
ResolvedRuleChainMetaData,
RuleChain,
RuleChainConnectionInfo,
RuleChainMetaData,
ruleChainNodeComponent,
RuleChainType,
ruleNodeTypeComponentTypes,
unknownNodeComponent
@ -34,14 +31,13 @@ import { ComponentDescriptorService } from './component-descriptor.service';
import {
IRuleNodeConfigurationComponent,
LinkLabel,
RuleNodeComponentDescriptor,
RuleNodeComponentDescriptor, RuleNodeConfiguration,
TestScriptInputParams,
TestScriptResult
} from '@app/shared/models/rule-node.models';
import { ResourcesService } from '../services/resources.service';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { EntityType } from '@shared/models/entity-type.models';
import { deepClone, snakeCase } from '@core/utils';
import { DebugRuleNodeEventBody } from '@app/shared/models/event.models';
import { Edge } from '@shared/models/edge.models';
@ -63,7 +59,8 @@ export class RuleChainService {
private translate: TranslateService
) { }
public getRuleChains(pageLink: PageLink, type: RuleChainType = RuleChainType.CORE, config?: RequestConfig): Observable<PageData<RuleChain>> {
public getRuleChains(pageLink: PageLink, type: RuleChainType = RuleChainType.CORE,
config?: RequestConfig): Observable<PageData<RuleChain>> {
return this.http.get<PageData<RuleChain>>(`/api/ruleChains${pageLink.toQuery()}&type=${type}`,
defaultHttpOptionsFromConfig(config));
}
@ -72,6 +69,10 @@ export class RuleChainService {
return this.http.get<RuleChain>(`/api/ruleChain/${ruleChainId}`, defaultHttpOptionsFromConfig(config));
}
public getRuleChainOutputLabels(ruleChainId: string, config?: RequestConfig): Observable<Array<string>> {
return this.http.get<Array<string>>(`/api/ruleChain/${ruleChainId}/output/labels`, defaultHttpOptionsFromConfig(config));
}
public createDefaultRuleChain(ruleChainName: string, config?: RequestConfig): Observable<RuleChain> {
return this.http.post<RuleChain>('/api/ruleChain/device/default', {
name: ruleChainName
@ -94,32 +95,10 @@ export class RuleChainService {
return this.http.get<RuleChainMetaData>(`/api/ruleChain/${ruleChainId}/metadata`, defaultHttpOptionsFromConfig(config));
}
public getResolvedRuleChainMetadata(ruleChainId: string, config?: RequestConfig): Observable<ResolvedRuleChainMetaData> {
return this.getRuleChainMetadata(ruleChainId, config).pipe(
mergeMap((ruleChainMetaData) => this.resolveRuleChainMetadata(ruleChainMetaData))
);
}
public saveRuleChainMetadata(ruleChainMetaData: RuleChainMetaData, config?: RequestConfig): Observable<RuleChainMetaData> {
return this.http.post<RuleChainMetaData>('/api/ruleChain/metadata', ruleChainMetaData, defaultHttpOptionsFromConfig(config));
}
public saveAndGetResolvedRuleChainMetadata(ruleChainMetaData: RuleChainMetaData,
config?: RequestConfig): Observable<ResolvedRuleChainMetaData> {
return this.saveRuleChainMetadata(ruleChainMetaData, config).pipe(
mergeMap((savedRuleChainMetaData) => this.resolveRuleChainMetadata(savedRuleChainMetaData))
);
}
public resolveRuleChainMetadata(ruleChainMetaData: RuleChainMetaData): Observable<ResolvedRuleChainMetaData> {
return this.resolveTargetRuleChains(ruleChainMetaData.ruleChainConnections).pipe(
map((targetRuleChainsMap) => {
const resolvedRuleChainMetadata: ResolvedRuleChainMetaData = {...ruleChainMetaData, targetRuleChainsMap};
return resolvedRuleChainMetadata;
})
);
}
public getRuleNodeComponents(modulesMap: IModulesMap, ruleChainType: RuleChainType, config?: RequestConfig):
Observable<Array<RuleNodeComponentDescriptor>> {
if (this.ruleNodeComponentsMap.get(ruleChainType)) {
@ -130,7 +109,6 @@ export class RuleChainService {
return this.resolveRuleNodeComponentsUiResources(components, modulesMap).pipe(
map((ruleNodeComponents) => {
this.ruleNodeComponentsMap.set(ruleChainType, ruleNodeComponents);
this.ruleNodeComponentsMap.get(ruleChainType).push(ruleChainNodeComponent);
this.ruleNodeComponentsMap.get(ruleChainType).sort(
(comp1, comp2) => {
let result = comp1.type.toString().localeCompare(comp2.type.toString());
@ -180,6 +158,14 @@ export class RuleChainService {
return component.configurationDescriptor.nodeDefinition.customRelations;
}
public ruleNodeSourceRuleChainId(component: RuleNodeComponentDescriptor, config: RuleNodeConfiguration): string {
if (component.configurationDescriptor.nodeDefinition.ruleChainNode) {
return config?.ruleChainId;
} else {
return null;
}
}
public getLatestRuleNodeDebugInput(ruleNodeId: string, config?: RequestConfig): Observable<DebugRuleNodeEventBody> {
return this.http.get<DebugRuleNodeEventBody>(`/api/ruleNode/${ruleNodeId}/debugIn`, defaultHttpOptionsFromConfig(config));
}
@ -188,26 +174,6 @@ export class RuleChainService {
return this.http.post<TestScriptResult>('/api/ruleChain/testScript', inputParams, defaultHttpOptionsFromConfig(config));
}
private resolveTargetRuleChains(ruleChainConnections: Array<RuleChainConnectionInfo>): Observable<{[ruleChainId: string]: RuleChain}> {
if (ruleChainConnections && ruleChainConnections.length) {
const tasks: Observable<RuleChain>[] = [];
ruleChainConnections.forEach((connection) => {
tasks.push(this.resolveRuleChain(connection.targetRuleChainId.id));
});
return forkJoin(tasks).pipe(
map((ruleChains) => {
const ruleChainsMap: {[ruleChainId: string]: RuleChain} = {};
ruleChains.forEach((ruleChain) => {
ruleChainsMap[ruleChain.id.id] = ruleChain;
});
return ruleChainsMap;
})
);
} else {
return of({} as {[ruleChainId: string]: RuleChain});
}
}
private loadRuleNodeComponents(ruleChainType: RuleChainType, config?: RequestConfig): Observable<Array<RuleNodeComponentDescriptor>> {
return this.componentDescriptorService.getComponentDescriptorsByTypes(ruleNodeTypeComponentTypes, ruleChainType, config).pipe(
map((components) => {
@ -228,7 +194,7 @@ export class RuleChainService {
tasks.push(this.resolveRuleNodeComponentUiResources(component, modulesMap));
});
return forkJoin(tasks).pipe(
catchError((err) => {
catchError(() => {
return of(components);
})
);
@ -268,7 +234,7 @@ export class RuleChainService {
));
}
return forkJoin(tasks).pipe(
map((res) => {
map(() => {
return component;
}),
catchError(() => {
@ -281,21 +247,6 @@ export class RuleChainService {
}
}
private resolveRuleChain(ruleChainId: string): Observable<RuleChain> {
return this.getRuleChain(ruleChainId, {ignoreErrors: true}).pipe(
map(ruleChain => ruleChain),
catchError((err) => {
const ruleChain = {
id: {
entityType: EntityType.RULE_CHAIN,
id: ruleChainId
}
} as RuleChain;
return of(ruleChain);
})
);
}
public getEdgeRuleChains(edgeId: string, pageLink: PageLink, config?: RequestConfig): Observable<PageData<RuleChain>> {
return this.http.get<PageData<RuleChain>>(`/api/edge/${edgeId}/ruleChains${pageLink.toQuery()}`,
defaultHttpOptionsFromConfig(config));

9
ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts

@ -423,7 +423,7 @@ export class ImportExportService {
public importRuleChain(expectedRuleChainType: RuleChainType): Observable<RuleChainImport> {
return this.openImportDialog('rulechain.import', 'rulechain.rulechain-file').pipe(
mergeMap((ruleChainImport: RuleChainImport) => {
map((ruleChainImport: RuleChainImport) => {
if (!this.validateImportedRuleChain(ruleChainImport)) {
this.store.dispatch(new ActionNotificationShow(
{message: this.translate.instant('rulechain.invalid-rulechain-file-error'),
@ -435,12 +435,7 @@ export class ImportExportService {
type: 'error'}));
throw new Error('Invalid rule chain type');
} else {
return this.ruleChainService.resolveRuleChainMetadata(ruleChainImport.metadata).pipe(
map((resolvedMetadata) => {
ruleChainImport.resolvedMetadata = resolvedMetadata;
return ruleChainImport;
})
);
return ruleChainImport;
}
}),
catchError((err) => {

8
ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts

@ -18,7 +18,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { EntitiesTableComponent } from '@home/components/entity/entities-table.component';
import { Authority } from '@shared/models/authority.enum';
import { EdgesTableConfigResolver } from '@home/pages/edge/edges-table-config.resolver'
import { EdgesTableConfigResolver } from '@home/pages/edge/edges-table-config.resolver';
import { AssetsTableConfigResolver } from '@home/pages/asset/assets-table-config.resolver';
import { DevicesTableConfigResolver } from '@home/pages/device/devices-table-config.resolver';
import { EntityViewsTableConfigResolver } from '@home/pages/entity-view/entity-views-table-config.resolver';
@ -32,7 +32,7 @@ import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { RuleChainType } from '@shared/models/rule-chain.models';
import {
importRuleChainBreadcumbLabelFunction,
ResolvedRuleChainMetaDataResolver,
RuleChainMetaDataResolver,
ruleChainBreadcumbLabelFunction,
RuleChainImportGuard,
RuleChainResolver,
@ -181,7 +181,7 @@ const routes: Routes = [
},
resolve: {
ruleChain: RuleChainResolver,
ruleChainMetaData: ResolvedRuleChainMetaDataResolver,
ruleChainMetaData: RuleChainMetaDataResolver,
ruleNodeComponents: RuleNodeComponentsResolver,
tooltipster: TooltipsterResolver
}
@ -243,7 +243,7 @@ const routes: Routes = [
},
resolve: {
ruleChain: RuleChainResolver,
ruleChainMetaData: ResolvedRuleChainMetaDataResolver,
ruleChainMetaData: RuleChainMetaDataResolver,
ruleNodeComponents: RuleNodeComponentsResolver,
tooltipster: TooltipsterResolver
}

3
ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html

@ -34,7 +34,8 @@
required
formControlName="link"
[allowedLabels]="labels"
[allowCustom]="allowCustomLabels">
[allowCustom]="allowCustomLabels"
[sourceRuleChainId]="sourceRuleChainId">
</tb-rule-node-link>
</fieldset>
</div>

74
ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts

@ -25,7 +25,8 @@ import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material
import { MatChipInputEvent, MatChipList } from '@angular/material/chips';
import { TranslateService } from '@ngx-translate/core';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { map, mergeMap, share, startWith } from 'rxjs/operators';
import { catchError, map, mergeMap, share, startWith } from 'rxjs/operators';
import { RuleChainService } from '@core/http/rule-chain.service';
@Component({
selector: 'tb-link-labels',
@ -68,6 +69,9 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan
@Input()
allowedLabels: {[label: string]: LinkLabel};
@Input()
sourceRuleChainId: string;
linksFormGroup: FormGroup;
modelValue: Array<string>;
@ -86,7 +90,8 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan
constructor(private fb: FormBuilder,
public truncate: TruncatePipe,
public translate: TranslateService) {
public translate: TranslateService,
private ruleChainService: RuleChainService) {
this.linksFormGroup = this.fb.group({
label: [null]
});
@ -115,7 +120,7 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (change.currentValue !== change.previousValue) {
if (['allowCustom', 'allowedLabels'].includes(propName)) {
if (['allowCustom', 'allowedLabels', 'sourceRuleChainId'].includes(propName)) {
reloadLabels = true;
}
}
@ -139,25 +144,50 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan
this.labelsList.length = 0;
this.labels.length = 0;
this.modelValue = value;
for (const label of Object.keys(this.allowedLabels)) {
this.labelsList.push({name: this.allowedLabels[label].name, value: this.allowedLabels[label].value});
}
if (value) {
value.forEach((label) => {
if (this.allowedLabels[label]) {
this.labels.push(deepClone(this.allowedLabels[label]));
} else {
this.labels.push({
name: label,
value: label
});
}
});
}
if (this.chipList && this.required) {
this.chipList.errorState = this.labels.length ? false : true;
this.prepareLabelsList().subscribe((labelsList) => {
this.labelsList = labelsList;
if (value) {
value.forEach((label) => {
if (this.allowedLabels[label]) {
this.labels.push(deepClone(this.allowedLabels[label]));
} else {
this.labels.push({
name: label,
value: label
});
}
});
}
if (this.chipList && this.required) {
this.chipList.errorState = !this.labels.length;
}
this.linksFormGroup.get('label').patchValue('', {emitEvent: true});
});
}
prepareLabelsList(): Observable<Array<LinkLabel>> {
const labelsList: Array<LinkLabel> = [];
if (this.sourceRuleChainId) {
return this.ruleChainService.getRuleChainOutputLabels(this.sourceRuleChainId, {ignoreErrors: true}).pipe(
map((labels) => {
for (const label of labels) {
labelsList.push({
name: label,
value: label
});
}
return labelsList;
}),
catchError(() => {
return of(labelsList);
})
);
} else {
for (const label of Object.keys(this.allowedLabels)) {
labelsList.push({name: this.allowedLabels[label].name, value: this.allowedLabels[label].value});
}
return of(labelsList);
}
this.linksFormGroup.get('label').patchValue('', {emitEvent: true});
}
displayLabelFn(label?: LinkLabel): string | undefined {
@ -165,7 +195,7 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan
}
textIsNotEmpty(text: string): boolean {
return (text && text != null && text.length > 0) ? true : false;
return (text && text.length > 0);
}
createLinkLabel($event: Event, value: string) {

2
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-colors.scss

@ -34,7 +34,7 @@
background-color: #fbc766;
}
&.tb-rule-chain-type {
&.tb-flow-type {
background-color: #d6c4f1;
}

9
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts

@ -37,6 +37,7 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { TranslateService } from '@ngx-translate/core';
import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component';
import { deepClone } from '@core/utils';
import { RuleChainType } from '@shared/models/rule-chain.models';
@Component({
selector: 'tb-rule-node-config',
@ -69,6 +70,12 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On
@Input()
ruleNodeId: string;
@Input()
ruleChainId: string;
@Input()
ruleChainType: RuleChainType;
nodeDefinitionValue: RuleNodeDefinition;
@Input()
@ -186,6 +193,8 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On
this.definedConfigComponentRef = this.definedConfigContainer.createComponent(factory);
this.definedConfigComponent = this.definedConfigComponentRef.instance;
this.definedConfigComponent.ruleNodeId = this.ruleNodeId;
this.definedConfigComponent.ruleChainId = this.ruleChainId;
this.definedConfigComponent.ruleChainType = this.ruleChainType;
this.definedConfigComponent.configuration = this.configuration;
this.changeSubscription = this.definedConfigComponent.configurationChanged.subscribe((configuration) => {
this.updateModel(configuration);

20
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<div class="tb-details-buttons" *ngIf="!isAdd && ruleNode.component.type === ruleNodeType.RULE_CHAIN">
<div class="tb-details-buttons" *ngIf="!isAdd && ruleNode.component?.configurationDescriptor?.nodeDefinition?.ruleChainNode">
<button mat-raised-button color="primary"
(click)="openRuleChain($event)">
{{'rulechain.open-rulechain' | translate }}
@ -23,7 +23,7 @@
</div>
<form [formGroup]="ruleNodeFormGroup" class="mat-padding">
<fieldset [disabled]="(isLoading$ | async) || !isEdit || isReadOnly">
<section *ngIf="ruleNode.component.type !== ruleNodeType.RULE_CHAIN">
<section>
<section fxLayout="column" fxLayout.gt-sm="row">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>rulenode.name</mat-label>
@ -43,6 +43,8 @@
<tb-rule-node-config #ruleNodeConfigComponent
formControlName="configuration"
[ruleNodeId]="ruleNode.ruleNodeId?.id"
[ruleChainId]="ruleChainId"
[ruleChainType]="ruleChainType"
[nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition">
</tb-rule-node-config>
<div formGroupName="additionalInfo" fxLayout="column">
@ -52,19 +54,5 @@
</mat-form-field>
</div>
</section>
<section *ngIf="ruleNode.component.type === ruleNodeType.RULE_CHAIN">
<tb-entity-autocomplete required
[excludeEntityIds]="[ruleChainId]"
[entityType]="entityType.RULE_CHAIN"
[entitySubtype]="ruleChainType"
formControlName="targetRuleChainId">
</tb-entity-autocomplete>
<div formGroupName="additionalInfo" fxLayout="column">
<mat-form-field class="mat-block">
<mat-label translate>rulenode.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea>
</mat-form-field>
</div>
</section>
</fieldset>
</form>

62
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts

@ -75,28 +75,16 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O
this.ruleNodeFormSubscription = null;
}
if (this.ruleNode) {
if (this.ruleNode.component.type !== RuleNodeType.RULE_CHAIN) {
this.ruleNodeFormGroup = this.fb.group({
name: [this.ruleNode.name, [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*'), Validators.maxLength(255)]],
debugMode: [this.ruleNode.debugMode, []],
configuration: [this.ruleNode.configuration, [Validators.required]],
additionalInfo: this.fb.group(
{
description: [this.ruleNode.additionalInfo ? this.ruleNode.additionalInfo.description : ''],
}
)
});
} else {
this.ruleNodeFormGroup = this.fb.group({
targetRuleChainId: [this.ruleNode.targetRuleChainId, [Validators.required]],
additionalInfo: this.fb.group(
{
description: [this.ruleNode.additionalInfo ? this.ruleNode.additionalInfo.description : ''],
}
)
});
}
this.ruleNodeFormGroup = this.fb.group({
name: [this.ruleNode.name, [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*'), Validators.maxLength(255)]],
debugMode: [this.ruleNode.debugMode, []],
configuration: [this.ruleNode.configuration, [Validators.required]],
additionalInfo: this.fb.group(
{
description: [this.ruleNode.additionalInfo ? this.ruleNode.additionalInfo.description : ''],
}
)
});
this.ruleNodeFormSubscription = this.ruleNodeFormGroup.valueChanges.subscribe(() => {
this.updateRuleNode();
});
@ -107,23 +95,8 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O
private updateRuleNode() {
const formValue = this.ruleNodeFormGroup.value || {};
if (this.ruleNode.component.type === RuleNodeType.RULE_CHAIN) {
const targetRuleChainId: string = formValue.targetRuleChainId;
if (this.ruleNode.targetRuleChainId !== targetRuleChainId && targetRuleChainId) {
this.ruleChainService.getRuleChain(targetRuleChainId).subscribe(
(ruleChain) => {
this.ruleNode.name = ruleChain.name;
Object.assign(this.ruleNode, formValue);
}
);
} else {
Object.assign(this.ruleNode, formValue);
}
} else {
formValue.name = formValue.name.trim();
Object.assign(this.ruleNode, formValue);
}
formValue.name = formValue.name.trim();
Object.assign(this.ruleNode, formValue);
}
ngOnInit(): void {
@ -141,20 +114,19 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O
}
validate() {
if (this.ruleNode.component.type !== RuleNodeType.RULE_CHAIN) {
this.ruleNodeConfigComponent.validate();
}
this.ruleNodeConfigComponent.validate();
}
openRuleChain($event: Event) {
if ($event) {
$event.stopPropagation();
}
if (this.ruleNode.targetRuleChainId) {
const ruleChainId = this.ruleNodeFormGroup.get('configuration')?.value?.ruleChainId;
if (ruleChainId) {
if (this.ruleChainType === RuleChainType.EDGE) {
this.router.navigateByUrl(`/edgeManagement/ruleChains/${this.ruleNode.targetRuleChainId}`);
this.router.navigateByUrl(`/edgeManagement/ruleChains/${ruleChainId}`);
} else {
this.router.navigateByUrl(`/ruleChains/${this.ruleNode.targetRuleChainId}`);
this.router.navigateByUrl(`/ruleChains/${ruleChainId}`);
}
}
}

1
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.html

@ -22,6 +22,7 @@
formControlName="labels"
[allowedLabels]="allowedLabels"
[allowCustom]="allowCustom"
[sourceRuleChainId]="sourceRuleChainId"
></tb-link-labels>
</form>
</div>

3
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.ts

@ -57,6 +57,9 @@ export class RuleNodeLinkComponent implements ControlValueAccessor, OnInit {
@Input()
allowedLabels: {[label: string]: LinkLabel};
@Input()
sourceRuleChainId: string;
ruleNodeLinkFormGroup: FormGroup;
modelValue: FcRuleEdge;

3
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html

@ -158,7 +158,8 @@
#tbRuleNodeLink
[(ngModel)]="editingRuleNodeLink"
[allowedLabels]="editingRuleNodeLinkLabels"
[allowCustom]="editingRuleNodeAllowCustomLabels">
[allowCustom]="editingRuleNodeAllowCustomLabels"
[sourceRuleChainId]="editingRuleNodeSourceRuleChainId">
</tb-rule-node-link>
</tb-details-panel>
</mat-drawer>

2
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss

@ -184,7 +184,7 @@
display: block;
position: absolute;
top: 11px;
right: -12px;
right: 10px;
border: 1px solid #FFFFFF;
border-radius: 4px;
line-height: 18px;

147
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts

@ -43,12 +43,10 @@ import { ActivatedRoute, Router } from '@angular/router';
import {
inputNodeComponent,
NodeConnectionInfo,
ResolvedRuleChainMetaData,
RuleChain,
RuleChainConnectionInfo,
RuleChainImport,
RuleChainMetaData,
ruleChainNodeComponent, RuleChainType
RuleChainType
} from '@shared/models/rule-chain.models';
import { FcItemInfo, FlowchartConstants, NgxFlowchartComponent, UserCallbacks } from 'ngx-flowchart';
import {
@ -75,7 +73,6 @@ import { DialogComponent } from '@shared/components/dialog.component';
import { MatMenuTrigger } from '@angular/material/menu';
import { ItemBufferService, RuleNodeConnection } from '@core/services/item-buffer.service';
import { Hotkey } from 'angular2-hotkeys';
import { EntityType } from '@shared/models/entity-type.models';
import { DebugEventType, EventType } from '@shared/models/event.models';
import Timeout = NodeJS.Timeout;
@ -130,6 +127,7 @@ export class RuleChainPageComponent extends PageComponent
editingRuleNodeIndex = -1;
editingRuleNodeAllowCustomLabels = false;
editingRuleNodeLinkLabels: {[label: string]: LinkLabel};
editingRuleNodeSourceRuleChainId: string;
@ViewChild('tbRuleNode') ruleNodeComponent: RuleNodeDetailsComponent;
@ViewChild('tbRuleNodeLink') ruleNodeLinkComponent: RuleNodeLinkComponent;
@ -147,7 +145,7 @@ export class RuleChainPageComponent extends PageComponent
ruleNodeTypeSearch = '';
ruleChain: RuleChain;
ruleChainMetaData: ResolvedRuleChainMetaData;
ruleChainMetaData: RuleChainMetaData;
ruleChainModel: FcRuleNodeModel = {
nodes: [],
@ -179,16 +177,11 @@ export class RuleChainPageComponent extends PageComponent
createEdge: (event, edge: FcRuleEdge) => {
const sourceNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source) as FcRuleNode;
if (sourceNode.component.type === RuleNodeType.INPUT) {
const destNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.destination) as FcRuleNode;
if (destNode.component.type === RuleNodeType.RULE_CHAIN) {
return NEVER;
} else {
const found = this.ruleChainModel.edges.find(theEdge => theEdge.source === (this.inputConnectorId + ''));
if (found) {
this.ruleChainCanvas.modelService.edges.delete(found);
}
return of(edge);
const found = this.ruleChainModel.edges.find(theEdge => theEdge.source === (this.inputConnectorId + ''));
if (found) {
this.ruleChainCanvas.modelService.edges.delete(found);
}
return of(edge);
} else {
if (edge.label) {
if (!edge.labels) {
@ -198,8 +191,9 @@ export class RuleChainPageComponent extends PageComponent
} else {
const labels = this.ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
const allowCustomLabels = this.ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component);
const sourceRuleChainId = this.ruleChainService.ruleNodeSourceRuleChainId(sourceNode.component, sourceNode.configuration);
this.enableHotKeys = false;
return this.addRuleNodeLink(edge, labels, allowCustomLabels).pipe(
return this.addRuleNodeLink(edge, labels, allowCustomLabels, sourceRuleChainId).pipe(
tap(() => {
this.enableHotKeys = true;
}),
@ -294,7 +288,7 @@ export class RuleChainPageComponent extends PageComponent
if (this.isImport) {
const ruleChainImport: RuleChainImport = this.itembuffer.getRuleChainImport();
this.ruleChain = ruleChainImport.ruleChain;
this.ruleChainMetaData = ruleChainImport.resolvedMetadata;
this.ruleChainMetaData = ruleChainImport.metadata;
} else {
this.ruleChain = this.route.snapshot.data.ruleChain;
this.ruleChainMetaData = this.route.snapshot.data.ruleChainMetaData;
@ -601,63 +595,6 @@ export class RuleChainPageComponent extends PageComponent
}
});
}
if (this.ruleChainMetaData.ruleChainConnections) {
const ruleChainsMap = this.ruleChainMetaData.targetRuleChainsMap;
const ruleChainNodesMap: {[ruleChainNodeId: string]: FcRuleNode} = {};
const ruleChainEdgeMap: {[edgeKey: string]: FcRuleEdge} = {};
this.ruleChainMetaData.ruleChainConnections.forEach((ruleChainConnection) => {
const ruleChain = ruleChainsMap[ruleChainConnection.targetRuleChainId.id];
if (ruleChainConnection.additionalInfo && ruleChainConnection.additionalInfo.ruleChainNodeId) {
let ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId];
if (!ruleChainNode) {
ruleChainNode = {
id: 'rule-chain-node-' + this.nextNodeID++,
name: ruleChain.name ? ruleChain.name : 'Unresolved',
targetRuleChainId: ruleChain.name ? ruleChainConnection.targetRuleChainId.id : null,
error: ruleChain.name ? undefined : this.translate.instant('rulenode.invalid-target-rulechain'),
additionalInfo: ruleChainConnection.additionalInfo,
x: Math.round(ruleChainConnection.additionalInfo.layoutX),
y: Math.round(ruleChainConnection.additionalInfo.layoutY),
component: ruleChainNodeComponent,
nodeClass: ruleNodeTypeDescriptors.get(RuleNodeType.RULE_CHAIN).nodeClass,
icon: ruleNodeTypeDescriptors.get(RuleNodeType.RULE_CHAIN).icon,
connectors: [
{
type: FlowchartConstants.leftConnectorType,
id: (this.nextConnectorID++) + ''
}
],
ruleChainType: this.ruleChainType
};
ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId] = ruleChainNode;
this.ruleChainModel.nodes.push(ruleChainNode);
}
const sourceNode = nodes[ruleChainConnection.fromIndex];
if (sourceNode) {
const connectors = sourceNode.connectors.filter(connector => connector.type === FlowchartConstants.rightConnectorType);
if (connectors && connectors.length) {
const sourceId = connectors[0].id;
const destId = ruleChainNode.connectors[0].id;
const edgeKey = sourceId + '_' + destId;
let ruleChainEdge = ruleChainEdgeMap[edgeKey];
if (!ruleChainEdge) {
ruleChainEdge = {
source: sourceId,
destination: destId,
label: ruleChainConnection.type,
labels: [ruleChainConnection.type]
};
ruleChainEdgeMap[edgeKey] = ruleChainEdge;
this.ruleChainModel.edges.push(ruleChainEdge);
} else {
ruleChainEdge.label += ' / ' + ruleChainConnection.type;
ruleChainEdge.labels.push(ruleChainConnection.type);
}
}
}
}
});
}
if (this.ruleChainCanvas) {
this.ruleChainCanvas.adjustCanvasSize(true);
}
@ -918,6 +855,8 @@ export class RuleChainPageComponent extends PageComponent
this.editingRuleNode = null;
this.editingRuleNodeLinkLabels = this.ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
this.editingRuleNodeAllowCustomLabels = this.ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component);
this.editingRuleNodeSourceRuleChainId =
this.ruleChainService.ruleNodeSourceRuleChainId(sourceNode.component, sourceNode.configuration);
this.isEditingRuleNodeLink = true;
this.editingRuleNodeLinkIndex = this.ruleChainModel.edges.indexOf(edge);
this.editingRuleNodeLink = deepClone(edge);
@ -1093,7 +1032,7 @@ export class RuleChainPageComponent extends PageComponent
const type = ruleNodeTypeDescriptors.get(ruleNodeType);
this.displayTooltip(event,
'<div class="tb-rule-node-tooltip tb-lib-tooltip">' +
'<div id="tb-node-content" layout="column">' +
'<div id="tb-node-content">' +
'<div class="tb-node-title">' + this.translate.instant(type.name) + '</div>' +
'<div class="tb-node-details">' + this.translate.instant(type.details) + '</div>' +
'</div>' +
@ -1104,7 +1043,7 @@ export class RuleChainPageComponent extends PageComponent
displayLibNodeDescriptionTooltip(event: MouseEvent, node: FcRuleNodeType) {
this.displayTooltip(event,
'<div class="tb-rule-node-tooltip tb-lib-tooltip">' +
'<div id="tb-node-content" layout="column">' +
'<div id="tb-node-content">' +
'<div class="tb-node-title">' + node.component.name + '</div>' +
'<div class="tb-node-description">' + node.component.configurationDescriptor.nodeDefinition.description + '</div>' +
'<div class="tb-node-details">' + node.component.configurationDescriptor.nodeDefinition.details + '</div>' +
@ -1129,7 +1068,7 @@ export class RuleChainPageComponent extends PageComponent
}
}
let tooltipContent = '<div class="tb-rule-node-tooltip">' +
'<div id="tb-node-content" layout="column">' +
'<div id="tb-node-content">' +
'<div class="tb-node-title">' + name + '</div>' +
'<div class="tb-node-description">' + desc + '</div>';
if (details) {
@ -1189,7 +1128,7 @@ export class RuleChainPageComponent extends PageComponent
resetDebugModeInAllNodes() {
let changed = false;
this.ruleChainModel.nodes.forEach((node) => {
if (node.component.type !== RuleNodeType.INPUT && node.component.type !== RuleNodeType.RULE_CHAIN) {
if (node.component.type !== RuleNodeType.INPUT) {
changed = changed || node.debugMode;
node.debugMode = false;
}
@ -1223,12 +1162,11 @@ export class RuleChainPageComponent extends PageComponent
const ruleChainMetaData: RuleChainMetaData = {
ruleChainId: this.ruleChain.id,
nodes: [],
connections: [],
ruleChainConnections: []
connections: []
};
const nodes: FcRuleNode[] = [];
this.ruleChainModel.nodes.forEach((node) => {
if (node.component.type !== RuleNodeType.INPUT && node.component.type !== RuleNodeType.RULE_CHAIN) {
if (node.component.type !== RuleNodeType.INPUT) {
const ruleNode: RuleNode = {
id: node.ruleNodeId,
type: node.component.clazz,
@ -1253,35 +1191,19 @@ export class RuleChainPageComponent extends PageComponent
const destNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.destination);
if (sourceNode.component.type !== RuleNodeType.INPUT) {
const fromIndex = nodes.indexOf(sourceNode);
if (destNode.component.type === RuleNodeType.RULE_CHAIN) {
const ruleChainConnection = {
fromIndex,
targetRuleChainId: {entityType: EntityType.RULE_CHAIN, id: destNode.targetRuleChainId},
additionalInfo: destNode.additionalInfo ? destNode.additionalInfo : {}
} as RuleChainConnectionInfo;
ruleChainConnection.additionalInfo.layoutX = Math.round(destNode.x);
ruleChainConnection.additionalInfo.layoutY = Math.round(destNode.y);
ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id;
edge.labels.forEach((label) => {
const newRuleChainConnection = deepClone(ruleChainConnection);
newRuleChainConnection.type = label;
ruleChainMetaData.ruleChainConnections.push(newRuleChainConnection);
});
} else {
const toIndex = nodes.indexOf(destNode);
const nodeConnection = {
fromIndex,
toIndex
} as NodeConnectionInfo;
edge.labels.forEach((label) => {
const newNodeConnection = deepClone(nodeConnection);
newNodeConnection.type = label;
ruleChainMetaData.connections.push(newNodeConnection);
});
}
const toIndex = nodes.indexOf(destNode);
const nodeConnection = {
fromIndex,
toIndex
} as NodeConnectionInfo;
edge.labels.forEach((label) => {
const newNodeConnection = deepClone(nodeConnection);
newNodeConnection.type = label;
ruleChainMetaData.connections.push(newNodeConnection);
});
}
});
this.ruleChainService.saveAndGetResolvedRuleChainMetadata(ruleChainMetaData).subscribe((savedRuleChainMetaData) => {
this.ruleChainService.saveRuleChainMetadata(ruleChainMetaData).subscribe((savedRuleChainMetaData) => {
this.ruleChainMetaData = savedRuleChainMetaData;
if (this.isImport) {
this.isDirtyValue = false;
@ -1346,7 +1268,8 @@ export class RuleChainPageComponent extends PageComponent
);
}
addRuleNodeLink(link: FcRuleEdge, labels: {[label: string]: LinkLabel}, allowCustomLabels: boolean): Observable<FcRuleEdge> {
addRuleNodeLink(link: FcRuleEdge, labels: {[label: string]: LinkLabel},
allowCustomLabels: boolean, sourceRuleChainId: string): Observable<FcRuleEdge> {
return this.dialog.open<AddRuleNodeLinkDialogComponent, AddRuleNodeLinkDialogData,
FcRuleEdge>(AddRuleNodeLinkDialogComponent, {
disableClose: true,
@ -1354,7 +1277,8 @@ export class RuleChainPageComponent extends PageComponent
data: {
link,
labels,
allowCustomLabels
allowCustomLabels,
sourceRuleChainId
}
}).afterClosed();
}
@ -1384,7 +1308,7 @@ export class RuleChainPageComponent extends PageComponent
}
);
const content = '<div class="tb-rule-node-error-tooltip">' +
'<div id="tooltip-content" layout="column">' +
'<div id="tooltip-content">' +
'<div class="tb-node-details">' + node.error + '</div>' +
'</div>' +
'</div>';
@ -1452,6 +1376,7 @@ export interface AddRuleNodeLinkDialogData {
link: FcRuleEdge;
labels: {[label: string]: LinkLabel};
allowCustomLabels: boolean;
sourceRuleChainId: string;
}
@Component({
@ -1468,6 +1393,7 @@ export class AddRuleNodeLinkDialogComponent extends DialogComponent<AddRuleNodeL
link: FcRuleEdge;
labels: {[label: string]: LinkLabel};
allowCustomLabels: boolean;
sourceRuleChainId: string;
submitted = false;
@ -1482,6 +1408,7 @@ export class AddRuleNodeLinkDialogComponent extends DialogComponent<AddRuleNodeL
this.link = this.data.link;
this.labels = this.data.labels;
this.allowCustomLabels = this.data.allowCustomLabels;
this.sourceRuleChainId = this.data.sourceRuleChainId;
this.ruleNodeLinkFormGroup = this.fb.group({
link: [deepClone(this.link), [Validators.required]]

12
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts

@ -32,7 +32,7 @@ import { RuleChainsTableConfigResolver } from '@modules/home/pages/rulechain/rul
import { from, Observable } from 'rxjs';
import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb';
import {
ResolvedRuleChainMetaData,
RuleChainMetaData,
RuleChain, RuleChainType
} from '@shared/models/rule-chain.models';
import { RuleChainService } from '@core/http/rule-chain.service';
@ -56,14 +56,14 @@ export class RuleChainResolver implements Resolve<RuleChain> {
}
@Injectable()
export class ResolvedRuleChainMetaDataResolver implements Resolve<ResolvedRuleChainMetaData> {
export class RuleChainMetaDataResolver implements Resolve<RuleChainMetaData> {
constructor(private ruleChainService: RuleChainService) {
}
resolve(route: ActivatedRouteSnapshot): Observable<ResolvedRuleChainMetaData> {
resolve(route: ActivatedRouteSnapshot): Observable<RuleChainMetaData> {
const ruleChainId = route.params.ruleChainId;
return this.ruleChainService.getResolvedRuleChainMetadata(ruleChainId);
return this.ruleChainService.getRuleChainMetadata(ruleChainId);
}
}
@ -160,7 +160,7 @@ const routes: Routes = [
},
resolve: {
ruleChain: RuleChainResolver,
ruleChainMetaData: ResolvedRuleChainMetaDataResolver,
ruleChainMetaData: RuleChainMetaDataResolver,
ruleNodeComponents: RuleNodeComponentsResolver,
tooltipster: TooltipsterResolver
}
@ -196,7 +196,7 @@ const routes: Routes = [
providers: [
RuleChainsTableConfigResolver,
RuleChainResolver,
ResolvedRuleChainMetaDataResolver,
RuleChainMetaDataResolver,
RuleNodeComponentsResolver,
TooltipsterResolver,
RuleChainImportGuard

4
ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html

@ -59,8 +59,8 @@
&times;
</div>
</section>
<section *ngIf="node.component.type === RuleNodeType.RULE_CHAIN && modelservice.isEditable()">
<div class="fc-nodeopen" (click)="openRuleChain($event, node)">
<section *ngIf="node.component?.configurationDescriptor?.nodeDefinition?.ruleChainNode && modelservice.isEditable()">
<div class="fc-nodeopen" (click)="openRuleChain($event, node)" (mouseenter)="displayOpenRuleChainTooltip($event, node)">
<mat-icon class="material-icons" svgIcon="mdi:login"></mat-icon>
</div>
</section>

43
ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts

@ -20,6 +20,7 @@ import { FcNodeComponent } from 'ngx-flowchart';
import { FcRuleNode, RuleNodeType } from '@shared/models/rule-node.models';
import { Router } from '@angular/router';
import { RuleChainType } from '@app/shared/models/rule-chain.models';
import { TranslateService } from '@ngx-translate/core';
@Component({
// tslint:disable-next-line:component-selector
@ -33,6 +34,7 @@ export class RuleNodeComponent extends FcNodeComponent implements OnInit {
RuleNodeType = RuleNodeType;
constructor(private sanitizer: DomSanitizer,
private translate: TranslateService,
private router: Router) {
super();
}
@ -48,13 +50,48 @@ export class RuleNodeComponent extends FcNodeComponent implements OnInit {
if ($event) {
$event.stopPropagation();
}
if (node.targetRuleChainId) {
if (node.configuration?.ruleChainId) {
if (node.ruleChainType === RuleChainType.EDGE) {
this.router.navigateByUrl(`/edgeManagement/ruleChains/${node.targetRuleChainId}`);
this.router.navigateByUrl(`/edgeManagement/ruleChains/${node.configuration?.ruleChainId}`);
} else {
this.router.navigateByUrl(`/ruleChains/${node.targetRuleChainId}`);
this.router.navigateByUrl(`/ruleChains/${node.configuration?.ruleChainId}`);
}
}
}
displayOpenRuleChainTooltip($event: MouseEvent, node: FcRuleNode) {
if ($event) {
$event.stopPropagation();
}
this.userNodeCallbacks.mouseLeave($event, node);
const tooltipContent = '<div class="tb-rule-node-tooltip">' +
'<div id="tb-node-content">' +
'<div class="tb-node-description">' + this.translate.instant('rulechain.open-rulechain') + '</div></div></div>';
const element = $($event.target);
element.tooltipster(
{
theme: 'tooltipster-shadow',
delay: 100,
trigger: 'custom',
triggerOpen: {
click: false,
tap: false
},
triggerClose: {
click: true,
tap: true,
scroll: true,
mouseleave: true
},
side: 'top',
distance: 12,
trackOrigin: true
}
);
const tooltip = element.tooltipster('instance');
const contentElement = $(tooltipContent);
tooltip.content(contentElement);
tooltip.open();
}
}

3
ui-ngx/src/app/shared/models/component-descriptor.models.ts

@ -21,7 +21,8 @@ export enum ComponentType {
FILTER = 'FILTER',
TRANSFORMATION = 'TRANSFORMATION',
ACTION = 'ACTION',
EXTERNAL = 'EXTERNAL'
EXTERNAL = 'EXTERNAL',
FLOW = 'FLOW'
}
export enum ComponentScope {

33
ui-ngx/src/app/shared/models/rule-chain.models.ts

@ -38,17 +38,11 @@ export interface RuleChainMetaData {
firstNodeIndex?: number;
nodes: Array<RuleNode>;
connections: Array<NodeConnectionInfo>;
ruleChainConnections: Array<RuleChainConnectionInfo>;
}
export interface ResolvedRuleChainMetaData extends RuleChainMetaData {
targetRuleChainsMap: {[ruleChainId: string]: RuleChain};
}
export interface RuleChainImport {
ruleChain: RuleChain;
metadata: RuleChainMetaData;
resolvedMetadata?: ResolvedRuleChainMetaData;
}
export interface NodeConnectionInfo {
@ -57,39 +51,16 @@ export interface NodeConnectionInfo {
type: string;
}
export interface RuleChainConnectionInfo {
fromIndex: number;
targetRuleChainId: RuleChainId;
additionalInfo: any;
type: string;
}
export const ruleNodeTypeComponentTypes: ComponentType[] =
[
ComponentType.FILTER,
ComponentType.ENRICHMENT,
ComponentType.TRANSFORMATION,
ComponentType.ACTION,
ComponentType.EXTERNAL
ComponentType.EXTERNAL,
ComponentType.FLOW
];
export const ruleChainNodeComponent: RuleNodeComponentDescriptor = {
type: RuleNodeType.RULE_CHAIN,
name: 'rule chain',
clazz: 'tb.internal.RuleChain',
configurationDescriptor: {
nodeDefinition: {
description: '',
details: 'Forwards incoming messages to specified Rule Chain',
inEnabled: true,
outEnabled: false,
relationTypes: [],
customRelations: false,
defaultConfiguration: {}
}
}
};
export const unknownNodeComponent: RuleNodeComponentDescriptor = {
type: RuleNodeType.UNKNOWN,
name: 'unknown',

23
ui-ngx/src/app/shared/models/rule-node.models.ts

@ -52,6 +52,7 @@ export interface RuleNodeDefinition {
outEnabled: boolean;
relationTypes: string[];
customRelations: boolean;
ruleChainNode?: boolean;
defaultConfiguration: RuleNodeConfiguration;
icon?: string;
iconUrl?: string;
@ -67,6 +68,8 @@ export interface RuleNodeConfigurationDescriptor {
export interface IRuleNodeConfigurationComponent {
ruleNodeId: string;
ruleChainId: string;
ruleChainType: RuleChainType;
configuration: RuleNodeConfiguration;
configurationChanged: Observable<RuleNodeConfiguration>;
validate();
@ -74,11 +77,16 @@ export interface IRuleNodeConfigurationComponent {
}
@Directive()
// tslint:disable-next-line:directive-class-suffix
export abstract class RuleNodeConfigurationComponent extends PageComponent implements
IRuleNodeConfigurationComponent, OnInit, AfterViewInit {
ruleNodeId: string;
ruleChainId: string;
ruleChainType: RuleChainType;
configurationValue: RuleNodeConfiguration;
private configurationSet = false;
@ -184,7 +192,7 @@ export enum RuleNodeType {
TRANSFORMATION = 'TRANSFORMATION',
ACTION = 'ACTION',
EXTERNAL = 'EXTERNAL',
RULE_CHAIN = 'RULE_CHAIN',
FLOW = 'FLOW',
UNKNOWN = 'UNKNOWN',
INPUT = 'INPUT'
}
@ -195,7 +203,7 @@ export const ruleNodeTypesLibrary = [
RuleNodeType.TRANSFORMATION,
RuleNodeType.ACTION,
RuleNodeType.EXTERNAL,
RuleNodeType.RULE_CHAIN,
RuleNodeType.FLOW,
];
export interface RuleNodeTypeDescriptor {
@ -260,12 +268,12 @@ export const ruleNodeTypeDescriptors = new Map<RuleNodeType, RuleNodeTypeDescrip
}
],
[
RuleNodeType.RULE_CHAIN,
RuleNodeType.FLOW,
{
value: RuleNodeType.RULE_CHAIN,
name: 'rulenode.type-rule-chain',
details: 'rulenode.type-rule-chain-details',
nodeClass: 'tb-rule-chain-type',
value: RuleNodeType.FLOW,
name: 'rulenode.type-flow',
details: 'rulenode.type-flow-details',
nodeClass: 'tb-flow-type',
icon: 'settings_ethernet'
}
],
@ -310,7 +318,6 @@ export interface FcRuleNode extends FcRuleNodeType {
additionalInfo?: any;
configuration?: RuleNodeConfiguration;
debugMode?: boolean;
targetRuleChainId?: string;
error?: string;
highlighted?: boolean;
componentClazz?: string;

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

@ -2617,6 +2617,8 @@
"type-external-details": "Interacts with external system",
"type-rule-chain": "Rule Chain",
"type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
"type-flow": "Flow",
"type-flow-details": "Organizes message flow",
"type-input": "Input",
"type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node",
"type-unknown": "Unknown",

Loading…
Cancel
Save