diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java
index 2186378527..ecc06f6303 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java
@@ -15,23 +15,26 @@
*/
package org.thingsboard.rule.engine.filter;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
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.server.common.data.StringUtils;
-import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.rule.engine.api.TbNodeException;
+import org.thingsboard.rule.engine.api.TbVersionedNode;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
+import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
+import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.List;
@@ -54,7 +57,9 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback;
"Output connections: True, False, Failure",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbFilterNodeCheckRelationConfig")
-public class TbCheckRelationNode implements TbNode {
+public class TbCheckRelationNode implements TbVersionedNode {
+
+ private static final String DIRECTION_PROPERTY_NAME = "direction";
private TbCheckRelationNodeConfiguration config;
private EntityId singleEntityId;
@@ -84,19 +89,19 @@ public class TbCheckRelationNode implements TbNode {
EntityId from;
EntityId to;
if (EntitySearchDirection.FROM.name().equals(config.getDirection())) {
- from = singleEntityId;
- to = msg.getOriginator();
- } else {
to = singleEntityId;
from = msg.getOriginator();
+ } else {
+ from = singleEntityId;
+ to = msg.getOriginator();
}
return ctx.getRelationService().checkRelationAsync(ctx.getTenantId(), from, to, config.getRelationType(), RelationTypeGroup.COMMON);
}
private ListenableFuture processList(TbContext ctx, TbMsg msg) {
ListenableFuture> relationListFuture = EntitySearchDirection.FROM.name().equals(config.getDirection()) ?
- ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON) :
- ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON);
+ ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON) :
+ ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON);
return Futures.transformAsync(relationListFuture, this::isEmptyList, ctx.getDbCallbackExecutor());
}
@@ -104,4 +109,25 @@ public class TbCheckRelationNode implements TbNode {
return entityRelations.isEmpty() ? Futures.immediateFuture(false) : Futures.immediateFuture(true);
}
+ @Override
+ public TbPair upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException {
+ if (fromVersion == 0) {
+ var newConfigObjectNode = (ObjectNode) oldConfiguration;
+ if (!newConfigObjectNode.has(DIRECTION_PROPERTY_NAME)) {
+ throw new TbNodeException("property to update: '" + DIRECTION_PROPERTY_NAME + "' doesn't exists in configuration!");
+ }
+ String direction = newConfigObjectNode.get(DIRECTION_PROPERTY_NAME).asText();
+ if ("TO".equals(direction)) {
+ newConfigObjectNode.put(DIRECTION_PROPERTY_NAME, EntitySearchDirection.FROM.name());
+ return new TbPair<>(true, newConfigObjectNode);
+ }
+ if ("FROM".equals(direction)) {
+ newConfigObjectNode.put(DIRECTION_PROPERTY_NAME, EntitySearchDirection.TO.name());
+ return new TbPair<>(true, newConfigObjectNode);
+ }
+ throw new TbNodeException("property to update: '" + DIRECTION_PROPERTY_NAME + "' has invalid value!");
+ }
+ return new TbPair<>(false, oldConfiguration);
+ }
+
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java
index ad5574ec8c..1a8ab7068d 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java
@@ -34,7 +34,7 @@ public class TbCheckRelationNodeConfiguration implements NodeConfiguration newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE));
verify(ctx, never()).tellFailure(any(), any());
- verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any());
TbMsg newMsg = newMsgCaptor.getValue();
assertThat(newMsg).isNotNull();
assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG);
@@ -179,7 +183,7 @@ class TbCheckRelationNodeTest {
config.setEntityId(assetId.getId().toString());
config.setDirection(EntitySearchDirection.TO.name());
- when(relationService.checkRelationAsync(TENANT_ID, DEVICE_ID, assetId, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(false));
+ when(relationService.checkRelationAsync(TENANT_ID, assetId, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(false));
node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)));
// WHEN
@@ -200,12 +204,12 @@ class TbCheckRelationNodeTest {
var config = new TbCheckRelationNodeConfiguration().defaultConfiguration();
config.setCheckForSingleEntity(false);
var entityRelation = new EntityRelation();
- entityRelation.setTo(DEVICE_ID);
- entityRelation.setFrom(new AssetId(UUID.randomUUID()));
+ entityRelation.setFrom(ORIGINATOR_ID);
+ entityRelation.setTo(new AssetId(UUID.randomUUID()));
entityRelation.setType(EntityRelation.CONTAINS_TYPE);
entityRelation.setTypeGroup(RelationTypeGroup.COMMON);
- when(relationService.findByToAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation)));
+ when(relationService.findByFromAndTypeAsync(TENANT_ID, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation)));
node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)));
// WHEN
@@ -215,7 +219,7 @@ class TbCheckRelationNodeTest {
ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE));
verify(ctx, never()).tellFailure(any(), any());
- verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any());
+ verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any());
TbMsg newMsg = newMsgCaptor.getValue();
assertThat(newMsg).isNotNull();
assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG);
@@ -227,7 +231,7 @@ class TbCheckRelationNodeTest {
var config = new TbCheckRelationNodeConfiguration().defaultConfiguration();
config.setCheckForSingleEntity(false);
- when(relationService.findByToAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList()));
+ when(relationService.findByFromAndTypeAsync(TENANT_ID, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList()));
node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)));
// WHEN
@@ -237,7 +241,7 @@ class TbCheckRelationNodeTest {
ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE));
verify(ctx, never()).tellFailure(any(), any());
- verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any());
+ verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any());
TbMsg newMsg = newMsgCaptor.getValue();
assertThat(newMsg).isNotNull();
assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG);
@@ -251,11 +255,11 @@ class TbCheckRelationNodeTest {
config.setDirection(EntitySearchDirection.TO.name());
var entityRelation = new EntityRelation();
entityRelation.setFrom(new AssetId(UUID.randomUUID()));
- entityRelation.setTo(DEVICE_ID);
+ entityRelation.setTo(ORIGINATOR_ID);
entityRelation.setType(EntityRelation.CONTAINS_TYPE);
entityRelation.setTypeGroup(RelationTypeGroup.COMMON);
- when(relationService.findByFromAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation)));
+ when(relationService.findByToAndTypeAsync(TENANT_ID, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(List.of(entityRelation)));
node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)));
// WHEN
@@ -265,7 +269,7 @@ class TbCheckRelationNodeTest {
ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.TRUE));
verify(ctx, never()).tellFailure(any(), any());
- verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any());
+ verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any());
TbMsg newMsg = newMsgCaptor.getValue();
assertThat(newMsg).isNotNull();
assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG);
@@ -278,7 +282,7 @@ class TbCheckRelationNodeTest {
config.setCheckForSingleEntity(false);
config.setDirection(EntitySearchDirection.TO.name());
- when(relationService.findByFromAndTypeAsync(TENANT_ID, DEVICE_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList()));
+ when(relationService.findByToAndTypeAsync(TENANT_ID, ORIGINATOR_ID, config.getRelationType(), RelationTypeGroup.COMMON)).thenReturn(Futures.immediateFuture(Collections.emptyList()));
node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)));
// WHEN
@@ -288,10 +292,25 @@ class TbCheckRelationNodeTest {
ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(TbNodeConnectionType.FALSE));
verify(ctx, never()).tellFailure(any(), any());
- verify(relationService, never()).findByToAndTypeAsync(any(), any(), anyString(), any());
+ verify(relationService, never()).findByFromAndTypeAsync(any(), any(), anyString(), any());
TbMsg newMsg = newMsgCaptor.getValue();
assertThat(newMsg).isNotNull();
assertThat(newMsg).isSameAs(EMPTY_POST_ATTRIBUTES_MSG);
}
+ @Test
+ void givenOldConfig_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception {
+ // GIVEN
+ var config = new TbCheckRelationNodeConfiguration().defaultConfiguration();
+ config.setEntityType(ORIGINATOR_ID.getEntityType().name());
+ config.setEntityId(ORIGINATOR_ID.getId().toString());
+ String oldConfig = "{\"checkForSingleEntity\":true,\"direction\":\"TO\",\"entityType\":\"" + config.getEntityType() + "\",\"entityId\":\"" + config.getEntityId() + "\",\"relationType\":\"Contains\"}";
+ JsonNode configJson = JacksonUtil.toJsonNode(oldConfig);
+ // WHEN
+ TbPair upgrade = node.upgrade(0, configJson);
+ // THEN
+ assertTrue(upgrade.getFirst());
+ assertEquals(config, JacksonUtil.treeToValue(upgrade.getSecond(), config.getClass()));
+ }
+
}
diff --git a/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md b/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md
index b21f450ae9..42f4ea6138 100644
--- a/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md
+++ b/ui-ngx/src/assets/help/en_US/rulenode/originator_fields_node_fields_templatization.md
@@ -12,7 +12,7 @@ Let's assume that we have two device types in our use case:
- `smart_door_lock`
- `motion_detector`
-Let's assume that device of type `dock_lock_sensor` and name `SDL-001` publish next type of messages to the system:
+Let's assume that device of type `smart_door_lock` and name `SDL-001` publish next type of messages to the system:
```json
{