From 40c957aabbc79808fa6114922b174b4e24da7a6c Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 15 Sep 2023 17:54:47 +0300 Subject: [PATCH 1/8] init commit for transformation nodes && improved error handling on init for fetchTo based nodes --- .../metadata/TbAbstractNodeWithFetchTo.java | 6 +- ...bAbstractTransformNodeWithTbMsgSource.java | 57 ++++++++++++ .../transform/TbChangeOriginatorNode.java | 10 ++- .../rule/engine/transform/TbCopyKeysNode.java | 89 +++++++++++-------- .../TbCopyKeysNodeConfiguration.java | 5 +- .../engine/transform/TbDeleteKeysNode.java | 88 ++++++++++-------- .../TbDeleteKeysNodeConfiguration.java | 5 +- .../rule/engine/transform/TbJsonPathNode.java | 8 +- .../engine/transform/TbRenameKeysNode.java | 83 ++++++++++------- .../TbRenameKeysNodeConfiguration.java | 7 +- .../engine/transform/TbSplitArrayMsgNode.java | 9 +- .../engine/transform/TbTransformMsgNode.java | 3 +- .../TbGetCustomerAttributeNodeTest.java | 3 +- .../TbGetCustomerDetailsNodeTest.java | 3 +- .../TbGetOriginatorFieldsNodeTest.java | 3 +- .../TbGetRelatedAttributeNodeTest.java | 2 +- .../TbGetTenantAttributeNodeTest.java | 3 +- .../metadata/TbGetTenantDetailsNodeTest.java | 3 +- .../engine/transform/TbCopyKeysNodeTest.java | 25 +++++- .../transform/TbDeleteKeysNodeTest.java | 25 +++++- .../transform/TbRenameKeysNodeTest.java | 25 +++++- 21 files changed, 315 insertions(+), 147 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNodeWithTbMsgSource.java diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java index a3b7fead3f..ef149aa4ad 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import java.util.Arrays; import java.util.NoSuchElementException; @Slf4j @@ -46,10 +47,9 @@ public abstract class TbAbstractNodeWithFetchTo upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException { + return fromVersion == 0 ? + upgradeToUseTbMsgSource((ObjectNode) oldConfiguration, getKeyToUpgradeFromVersionZero()) : + new TbPair<>(false, oldConfiguration); + } + + private TbPair upgradeToUseTbMsgSource(ObjectNode configToUpdate, String newProperty) throws TbNodeException { + if (!configToUpdate.has(FROM_METADATA_PROPERTY)) { + throw new TbNodeException("property to update: '" + FROM_METADATA_PROPERTY + "' doesn't exists in configuration!"); + } + var value = configToUpdate.get(FROM_METADATA_PROPERTY).asText(); + if ("true".equals(value)) { + configToUpdate.remove(FROM_METADATA_PROPERTY); + configToUpdate.put(newProperty, TbMsgSource.METADATA.name()); + return new TbPair<>(true, configToUpdate); + } + if ("false".equals(value)) { + configToUpdate.remove(FROM_METADATA_PROPERTY); + configToUpdate.put(newProperty, TbMsgSource.DATA.name()); + return new TbPair<>(true, configToUpdate); + } + throw new TbNodeException("property to update: '" + FROM_METADATA_PROPERTY + "' has unexpected value: " + + value + ". Allowed values: true or false!"); + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index 4d1a092097..9efc051e2b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -43,10 +43,14 @@ import java.util.NoSuchElementException; type = ComponentType.TRANSFORMATION, name = "change originator", configClazz = TbChangeOriginatorNodeConfiguration.class, - nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity/Alarm Originator", + nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity/Alarm Originator/Entity by name pattern", nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + - "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded.
" + - "Alarm Originator found only in case original Originator is Alarm entity.", + "If multiple related entities are found, only first entity is used as new originator, other entities are discarded.
" + + "Alarm Originator might be found only if the original Originator is Alarm entity.
" + + "Entity by name pattern lookup only if found only in case original Originator is Alarm entity.
" + + "Lookup of an entity by name pattern requires selection of entity type and name pattern to be specified in the configuration. " + + "Allowed entity types to select: 'DEVICE', 'ASSET', 'ENTITY_VIEW', 'EDGE' and 'USER'.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeChangeOriginatorConfig", icon = "find_replace" diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java index 8d9a7102d0..93b5ecb5d7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java @@ -21,87 +21,98 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; 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.util.TbNodeUtils; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgMetaData; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.regex.Pattern; +import java.util.stream.Collectors; @Slf4j @RuleNode( type = ComponentType.TRANSFORMATION, - name = "copy keys", + name = "copy key-values", + version = 1, configClazz = TbCopyKeysNodeConfiguration.class, - nodeDescription = "Copies the msg or metadata keys with specified key names selected in the list", - nodeDetails = "Will fetch fields values specified in list. If specified field is not part of msg or metadata fields it will be ignored." + - "Returns transformed messages via Success chain", + nodeDescription = "Copies key-values from message to message metadata or vice-versa.", + nodeDetails = "Fetches key-values from message or message metadata based on the keys list specified in the configuration " + + "and copies them into message metadata or message. Keys that are absent in the fetch source will be ignored. " + + "Use regular expression(s) as a key(s) to copy keys by pattern.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeCopyKeysConfig", icon = "content_copy" ) -public class TbCopyKeysNode implements TbNode { +public class TbCopyKeysNode extends TbAbstractTransformNodeWithTbMsgSource { private TbCopyKeysNodeConfiguration config; private List patternKeys; - private boolean fromMetadata; + private TbMsgSource copyFrom; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbCopyKeysNodeConfiguration.class); - this.fromMetadata = config.isFromMetadata(); - this.patternKeys = new ArrayList<>(); - config.getKeys().forEach(key -> { - this.patternKeys.add(Pattern.compile(key)); - }); + this.copyFrom = config.getCopyFrom(); + if (copyFrom == null) { + throw new TbNodeException("CopyFrom can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); + } + this.patternKeys = config.getKeys().stream().map(Pattern::compile).collect(Collectors.toList()); } @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - TbMsgMetaData metaData = msg.getMetaData(); + var metaDataCopy = msg.getMetaData().copy(); String msgData = msg.getData(); boolean msgChanged = false; JsonNode dataNode = JacksonUtil.toJsonNode(msgData); if (dataNode.isObject()) { - if (fromMetadata) { - ObjectNode msgDataNode = (ObjectNode) dataNode; - Map metaDataMap = metaData.getData(); - for (Map.Entry entry : metaDataMap.entrySet()) { - String keyData = entry.getKey(); - if (checkKey(keyData)) { - msgChanged = true; - msgDataNode.put(keyData, entry.getValue()); + switch (copyFrom) { + case METADATA: + ObjectNode msgDataNode = (ObjectNode) dataNode; + Map metaDataMap = metaDataCopy.getData(); + for (Map.Entry entry : metaDataMap.entrySet()) { + String keyData = entry.getKey(); + if (checkKey(keyData)) { + msgChanged = true; + msgDataNode.put(keyData, entry.getValue()); + } } - } - msgData = JacksonUtil.toString(msgDataNode); - } else { - Iterator> iteratorNode = dataNode.fields(); - while (iteratorNode.hasNext()) { - Map.Entry entry = iteratorNode.next(); - String keyData = entry.getKey(); - if (checkKey(keyData)) { - msgChanged = true; - metaData.putValue(keyData, JacksonUtil.toString(entry.getValue())); + msgData = JacksonUtil.toString(msgDataNode); + break; + case DATA: + Iterator> iteratorNode = dataNode.fields(); + while (iteratorNode.hasNext()) { + Map.Entry entry = iteratorNode.next(); + String keyData = entry.getKey(); + if (checkKey(keyData)) { + msgChanged = true; + metaDataCopy.putValue(keyData, JacksonUtil.toString(entry.getValue())); + } } - } + break; + default: + log.debug("Unexpected CopyFrom value: {}. Allowed values: {}", copyFrom, TbMsgSource.values()); + break; } } - if (msgChanged) { - ctx.tellSuccess(TbMsg.transformMsg(msg, metaData, msgData)); - } else { - ctx.tellSuccess(msg); - } + ctx.tellSuccess(msgChanged ? TbMsg.transformMsg(msg, metaDataCopy, msgData) : msg); + } + + @Override + protected String getKeyToUpgradeFromVersionZero() { + return "copyFrom"; } boolean checkKey(String key) { return patternKeys.stream().anyMatch(pattern -> pattern.matcher(key).matches()); } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeConfiguration.java index 6bf9f1e343..5c8d7edf4b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeConfiguration.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.transform; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.util.TbMsgSource; import java.util.Collections; import java.util.Set; @@ -24,14 +25,14 @@ import java.util.Set; @Data public class TbCopyKeysNodeConfiguration implements NodeConfiguration { - private boolean fromMetadata; + private TbMsgSource copyFrom; private Set keys; @Override public TbCopyKeysNodeConfiguration defaultConfiguration() { TbCopyKeysNodeConfiguration configuration = new TbCopyKeysNodeConfiguration(); configuration.setKeys(Collections.emptySet()); - configuration.setFromMetadata(false); + configuration.setCopyFrom(TbMsgSource.DATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java index 9ea31bc56d..4666016f78 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java @@ -21,84 +21,96 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; 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.util.TbNodeUtils; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.regex.Pattern; +import java.util.stream.Collectors; @Slf4j @RuleNode( type = ComponentType.TRANSFORMATION, - name = "delete keys", + name = "delete key-values", + version = 1, configClazz = TbDeleteKeysNodeConfiguration.class, - nodeDescription = "Removes keys from the msg data or metadata with the specified key names selected in the list", - nodeDetails = "Will fetch fields (regex) values specified in list. If specified field (regex) is not part of msg " + - "or metadata fields it will be ignored. Returns transformed messages via Success chain", + nodeDescription = "Removes key-values from message or metadata.", + nodeDetails = "Removes key-values from message or message metadata based on the keys list specified in the configuration. " + + "Use regular expression(s) as a key(s) to remove keys by pattern.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeDeleteKeysConfig", icon = "remove_circle" ) -public class TbDeleteKeysNode implements TbNode { +public class TbDeleteKeysNode extends TbAbstractTransformNodeWithTbMsgSource { private TbDeleteKeysNodeConfiguration config; private List patternKeys; - private boolean fromMetadata; + private TbMsgSource deleteFrom; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbDeleteKeysNodeConfiguration.class); - this.fromMetadata = config.isFromMetadata(); - this.patternKeys = new ArrayList<>(); - config.getKeys().forEach(key -> { - this.patternKeys.add(Pattern.compile(key)); - }); + this.deleteFrom = config.getDeleteFrom(); + if (deleteFrom == null) { + throw new TbNodeException("DeleteFrom can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); + } + this.patternKeys = config.getKeys().stream().map(Pattern::compile).collect(Collectors.toList()); } @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - TbMsgMetaData metaData = msg.getMetaData(); + TbMsgMetaData metaDataCopy = msg.getMetaData().copy(); String msgData = msg.getData(); List keysToDelete = new ArrayList<>(); - if (fromMetadata) { - Map metaDataMap = metaData.getData(); - metaDataMap.forEach((keyMetaData, valueMetaData) -> { - if (checkKey(keyMetaData)) { - keysToDelete.add(keyMetaData); - } - }); - keysToDelete.forEach(metaDataMap::remove); - metaData = new TbMsgMetaData(metaDataMap); - } else { - JsonNode dataNode = JacksonUtil.toJsonNode(msgData); - if (dataNode.isObject()) { - ObjectNode msgDataObject = (ObjectNode) dataNode; - dataNode.fields().forEachRemaining(entry -> { - String keyData = entry.getKey(); - if (checkKey(keyData)) { - keysToDelete.add(keyData); + switch (deleteFrom) { + case METADATA: + Map metaDataMap = metaDataCopy.getData(); + metaDataMap.forEach((keyMetaData, valueMetaData) -> { + if (checkKey(keyMetaData)) { + keysToDelete.add(keyMetaData); } }); - msgDataObject.remove(keysToDelete); - msgData = JacksonUtil.toString(msgDataObject); - } - } - if (keysToDelete.isEmpty()) { - ctx.tellSuccess(msg); - } else { - ctx.tellSuccess(TbMsg.transformMsg(msg, metaData, msgData)); + keysToDelete.forEach(metaDataMap::remove); + metaDataCopy = new TbMsgMetaData(metaDataMap); + break; + case DATA: + JsonNode dataNode = JacksonUtil.toJsonNode(msgData); + if (dataNode.isObject()) { + ObjectNode msgDataObject = (ObjectNode) dataNode; + dataNode.fields().forEachRemaining(entry -> { + String keyData = entry.getKey(); + if (checkKey(keyData)) { + keysToDelete.add(keyData); + } + }); + msgDataObject.remove(keysToDelete); + msgData = JacksonUtil.toString(msgDataObject); + } + break; + default: + log.debug("Unexpected DeleteFrom value: {}. Allowed values: {}", deleteFrom, TbMsgSource.values()); + break; } + ctx.tellSuccess(keysToDelete.isEmpty() ? msg : TbMsg.transformMsg(msg, metaDataCopy, msgData)); + } + + @Override + protected String getKeyToUpgradeFromVersionZero() { + return "deleteFrom"; } boolean checkKey(String key) { return patternKeys.stream().anyMatch(pattern -> pattern.matcher(key).matches()); } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeConfiguration.java index d0b22df409..d68b391df6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeConfiguration.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.transform; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.util.TbMsgSource; import java.util.Collections; import java.util.Set; @@ -24,14 +25,14 @@ import java.util.Set; @Data public class TbDeleteKeysNodeConfiguration implements NodeConfiguration { - private boolean fromMetadata; + private TbMsgSource deleteFrom; private Set keys; @Override public TbDeleteKeysNodeConfiguration defaultConfiguration() { TbDeleteKeysNodeConfiguration configuration = new TbDeleteKeysNodeConfiguration(); configuration.setKeys(Collections.emptySet()); - configuration.setFromMetadata(false); + configuration.setDeleteFrom(TbMsgSource.DATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java index 0e85a50c34..9454bd51c4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java @@ -38,10 +38,10 @@ import java.util.concurrent.ExecutionException; name = "json path", configClazz = TbJsonPathNodeConfiguration.class, nodeDescription = "Transforms incoming message body using JSONPath expression.", - nodeDetails = "JSONPath expression specifies a path to an element or a set of elements in a JSON structure.
" - + "'$' represents the root object or array.
" - + "If JSONPath expression evaluation failed, incoming message routes via Failure chain, " - + "otherwise Success chain is used.", + nodeDetails = "JSONPath expression specifies a path to an element or a set of elements in a JSON structure.
" + + "'$' represents the root object or array.
" + + "If JSONPath expression evaluation failed, incoming message routes via Failure chain.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, icon = "functions", configDirective = "tbTransformationNodeJsonPathConfig" diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java index 88e6dc1a8b..d6e4e0dc34 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java @@ -21,14 +21,15 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; 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.util.TbNodeUtils; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import java.util.Arrays; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -36,62 +37,78 @@ import java.util.concurrent.ExecutionException; @RuleNode( type = ComponentType.TRANSFORMATION, name = "rename keys", + version = 1, configClazz = TbRenameKeysNodeConfiguration.class, - nodeDescription = "Renames msg data or metadata keys to the new key names selected in the key mapping.", - nodeDetails = "If the key that is selected in the key mapping is missed in the selected msg source(data or metadata), it will be ignored." + - " Returns transformed messages via Success chain", + nodeDescription = "Renames message or message metadata key names to the new key names selected in the key mapping.", + nodeDetails = "If the key name that is selected in the key mapping is missed in the " + + "selected source(message or message metadata), it will be ignored.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeRenameKeysConfig", icon = "find_replace" ) -public class TbRenameKeysNode implements TbNode { +public class TbRenameKeysNode extends TbAbstractTransformNodeWithTbMsgSource { private TbRenameKeysNodeConfiguration config; private Map renameKeysMapping; - private boolean fromMetadata; + private TbMsgSource renameIn; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbRenameKeysNodeConfiguration.class); + this.renameIn = config.getRenameIn(); this.renameKeysMapping = config.getRenameKeysMapping(); - this.fromMetadata = config.isFromMetadata(); + if (renameIn == null) { + throw new TbNodeException("RenameIn can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); + } + if (renameKeysMapping == null || renameKeysMapping.isEmpty()) { + throw new TbNodeException("At least one mapping entry should be specified!"); + } } @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - TbMsgMetaData metaData = msg.getMetaData(); + TbMsgMetaData metaDataCopy = msg.getMetaData().copy(); String data = msg.getData(); boolean msgChanged = false; - if (fromMetadata) { - Map metaDataMap = metaData.getData(); - for (Map.Entry entry : renameKeysMapping.entrySet()) { - String nameKey = entry.getKey(); - if (metaDataMap.containsKey(nameKey)) { - msgChanged = true; - metaDataMap.put(entry.getValue(), metaDataMap.get(nameKey)); - metaDataMap.remove(nameKey); - } - } - metaData = new TbMsgMetaData(metaDataMap); - } else { - JsonNode dataNode = JacksonUtil.toJsonNode(data); - if (dataNode.isObject()) { - ObjectNode msgData = (ObjectNode) dataNode; + switch (renameIn) { + case METADATA: + Map metaDataMap = metaDataCopy.getData(); for (Map.Entry entry : renameKeysMapping.entrySet()) { String nameKey = entry.getKey(); - if (msgData.has(nameKey)) { + if (metaDataMap.containsKey(nameKey)) { msgChanged = true; - msgData.set(entry.getValue(), msgData.get(nameKey)); - msgData.remove(nameKey); + metaDataMap.put(entry.getValue(), metaDataMap.get(nameKey)); + metaDataMap.remove(nameKey); } } - data = JacksonUtil.toString(msgData); - } - } - if (msgChanged) { - ctx.tellSuccess(TbMsg.transformMsg(msg, metaData, data)); - } else { - ctx.tellSuccess(msg); + metaDataCopy = new TbMsgMetaData(metaDataMap); + break; + case DATA: + JsonNode dataNode = JacksonUtil.toJsonNode(data); + if (dataNode.isObject()) { + ObjectNode msgData = (ObjectNode) dataNode; + for (Map.Entry entry : renameKeysMapping.entrySet()) { + String nameKey = entry.getKey(); + if (msgData.has(nameKey)) { + msgChanged = true; + msgData.set(entry.getValue(), msgData.get(nameKey)); + msgData.remove(nameKey); + } + } + data = JacksonUtil.toString(msgData); + } + break; + default: + log.debug("Unexpected RenameIn value: {}. Allowed values: {}", renameIn, TbMsgSource.values()); + break; } + ctx.tellSuccess(msgChanged ? TbMsg.transformMsg(msg, metaDataCopy, data) : msg); } + + @Override + protected String getKeyToUpgradeFromVersionZero() { + return "renameIn"; + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeConfiguration.java index 0475ba4ca4..1e6d5e1483 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeConfiguration.java @@ -17,20 +17,21 @@ package org.thingsboard.rule.engine.transform; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.util.TbMsgSource; import java.util.Map; @Data public class TbRenameKeysNodeConfiguration implements NodeConfiguration { - private boolean fromMetadata; + private TbMsgSource renameIn; private Map renameKeysMapping; @Override public TbRenameKeysNodeConfiguration defaultConfiguration() { TbRenameKeysNodeConfiguration configuration = new TbRenameKeysNodeConfiguration(); - configuration.setRenameKeysMapping(Map.of("temp", "temperature")); - configuration.setFromMetadata(false); + configuration.setRenameKeysMapping(Map.of("temperatureCelsius", "temperature")); + configuration.setRenameIn(TbMsgSource.DATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java index 8959d12c9f..7a3e53c69c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java @@ -25,8 +25,8 @@ 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.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.queue.RuleEngineException; @@ -39,10 +39,11 @@ import java.util.concurrent.ExecutionException; type = ComponentType.TRANSFORMATION, name = "split array msg", configClazz = EmptyNodeConfiguration.class, - nodeDescription = "Split array message into several msgs", - nodeDetails = "Split the array fetched from the msg body. If the msg data is not a JSON array returns the " + nodeDescription = "Split array message into several messages", + nodeDetails = "Split the array fetched from the message. If the msg data is not a JSON array returns the " + "incoming message as outbound message with Failure chain, otherwise returns " - + "inner objects of the extracted array as separate messages via Success chain.", + + "inner objects of the extracted array as separate messages via Success chain.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, icon = "content_copy", configDirective = "tbNodeEmptyConfig" diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index ba78970c54..0cff412d39 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -39,7 +39,8 @@ import java.util.List; "msgType - is a Message type.
" + "Should return the following structure:
" + "{ msg: new payload,
   metadata: new metadata,
   msgType: new msgType }

" + - "All fields in resulting object are optional and will be taken from original message if not specified.", + "All fields in resulting object are optional and will be taken from original message if not specified.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeScriptConfig" ) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index 291614464a..d200ec70d8 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -59,6 +59,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -119,7 +120,7 @@ public class TbGetCustomerAttributeNodeTest { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + assertThat(exception.getMessage()).isEqualTo("FetchTo option can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); verify(ctxMock, never()).tellSuccess(any()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index 51625eafe7..16db712ef9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -57,6 +57,7 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.user.UserService; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; @@ -127,7 +128,7 @@ public class TbGetCustomerDetailsNodeTest { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + assertThat(exception.getMessage()).isEqualTo("FetchTo option can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); verify(ctxMock, never()).tellSuccess(any()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index d681deb9c0..ded7400ea5 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -40,6 +40,7 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.device.DeviceService; +import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.UUID; @@ -86,7 +87,7 @@ public class TbGetOriginatorFieldsNodeTest { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + assertThat(exception.getMessage()).isEqualTo("FetchTo option can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); verify(ctxMock, never()).tellSuccess(any()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java index dcf5c22ea5..b236c3097c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java @@ -127,7 +127,7 @@ public class TbGetRelatedAttributeNodeTest { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + assertThat(exception.getMessage()).isEqualTo("FetchTo option can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); verify(ctxMock, never()).tellSuccess(any()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java index 87b34cffe0..f0c0e76419 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java @@ -50,6 +50,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.timeseries.TimeseriesService; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -99,7 +100,7 @@ public class TbGetTenantAttributeNodeTest { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + assertThat(exception.getMessage()).isEqualTo("FetchTo option can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); verify(ctxMock, never()).tellSuccess(any()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java index bfbaa9a92e..93da628de0 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.tenant.TenantService; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; @@ -96,7 +97,7 @@ public class TbGetTenantDetailsNodeTest { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + assertThat(exception.getMessage()).isEqualTo("FetchTo option can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); verify(ctxMock, never()).tellSuccess(any()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java index e0ab34e35b..c44c3b5a24 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.transform; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,9 +25,11 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -37,6 +40,8 @@ import java.util.Set; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -59,7 +64,7 @@ public class TbCopyKeysNodeTest { ctx = mock(TbContext.class); config = new TbCopyKeysNodeConfiguration().defaultConfiguration(); config.setKeys(Set.of("TestKey_1", "TestKey_2", "TestKey_3", "(\\w*)Data(\\w*)")); - config.setFromMetadata(true); + config.setCopyFrom(TbMsgSource.METADATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node = spy(new TbCopyKeysNode()); node.init(ctx, nodeConfiguration); @@ -74,7 +79,7 @@ public class TbCopyKeysNodeTest { void givenDefaultConfig_whenVerify_thenOK() { TbCopyKeysNodeConfiguration defaultConfig = new TbCopyKeysNodeConfiguration().defaultConfiguration(); assertThat(defaultConfig.getKeys()).isEqualTo(Collections.emptySet()); - assertThat(defaultConfig.isFromMetadata()).isEqualTo(false); + assertThat(defaultConfig.getCopyFrom()).isEqualTo(TbMsgSource.DATA); } @Test @@ -95,7 +100,7 @@ public class TbCopyKeysNodeTest { @Test void givenMsgFromMsg_whenOnMsg_thenVerifyOutput() throws Exception { - config.setFromMetadata(false); + config.setCopyFrom(TbMsgSource.DATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -149,6 +154,20 @@ public class TbCopyKeysNodeTest { assertThat(newMsg).isSameAs(msg); } + @Test + void givenOldConfig_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception { + // GIVEN + var config = new TbCopyKeysNodeConfiguration().defaultConfiguration(); + var oldConfigJson = (ObjectNode) JacksonUtil.valueToTree(config); + oldConfigJson.remove("copyFrom"); + oldConfigJson.put("fromMetadata", "false"); + // WHEN + TbPair upgrade = node.upgrade(0, oldConfigJson); + // THEN + assertTrue(upgrade.getFirst()); + assertEquals(config, JacksonUtil.treeToValue(upgrade.getSecond(), config.getClass())); + } + private TbMsg getTbMsg(EntityId entityId, String data) { final Map mdMap = Map.of( "TestKey_1", "Test", diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java index 6eee16e7d3..042babb1b9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.transform; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,9 +25,11 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -37,6 +40,8 @@ import java.util.Set; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -59,7 +64,7 @@ public class TbDeleteKeysNodeTest { ctx = mock(TbContext.class); config = new TbDeleteKeysNodeConfiguration().defaultConfiguration(); config.setKeys(Set.of("TestKey_1", "TestKey_2", "TestKey_3", "(\\w*)Data(\\w*)")); - config.setFromMetadata(true); + config.setDeleteFrom(TbMsgSource.METADATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node = spy(new TbDeleteKeysNode()); node.init(ctx, nodeConfiguration); @@ -74,7 +79,7 @@ public class TbDeleteKeysNodeTest { void givenDefaultConfig_whenVerify_thenOK() { TbDeleteKeysNodeConfiguration defaultConfig = new TbDeleteKeysNodeConfiguration().defaultConfiguration(); assertThat(defaultConfig.getKeys()).isEqualTo(Collections.emptySet()); - assertThat(defaultConfig.isFromMetadata()).isEqualTo(false); + assertThat(defaultConfig.getDeleteFrom()).isEqualTo(TbMsgSource.DATA); } @Test @@ -95,7 +100,7 @@ public class TbDeleteKeysNodeTest { @Test void givenMsgFromMsg_whenOnMsg_thenVerifyOutput() throws Exception { - config.setFromMetadata(false); + config.setDeleteFrom(TbMsgSource.DATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -133,6 +138,20 @@ public class TbDeleteKeysNodeTest { assertThat(newMsg.getData()).isEqualTo(data); } + @Test + void givenOldConfig_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception { + // GIVEN + var config = new TbDeleteKeysNodeConfiguration().defaultConfiguration(); + var oldConfigJson = (ObjectNode) JacksonUtil.valueToTree(config); + oldConfigJson.remove("deleteFrom"); + oldConfigJson.put("fromMetadata", "false"); + // WHEN + TbPair upgrade = node.upgrade(0, oldConfigJson); + // THEN + assertTrue(upgrade.getFirst()); + assertEquals(config, JacksonUtil.treeToValue(upgrade.getSecond(), config.getClass())); + } + private TbMsg getTbMsg(EntityId entityId, String data) { final Map mdMap = Map.of( "TestKey_1", "Test", diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java index c602aab5df..ae911ea529 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.transform; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,9 +25,11 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -35,6 +38,8 @@ import java.util.Map; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -70,8 +75,8 @@ public class TbRenameKeysNodeTest { @Test void givenDefaultConfig_whenVerify_thenOK() { TbRenameKeysNodeConfiguration defaultConfig = new TbRenameKeysNodeConfiguration().defaultConfiguration(); - assertThat(defaultConfig.getRenameKeysMapping()).isEqualTo(Map.of("temp", "temperature")); - assertThat(defaultConfig.isFromMetadata()).isEqualTo(false); + assertThat(defaultConfig.getRenameKeysMapping()).isEqualTo(Map.of("temperatureCelsius", "temperature")); + assertThat(defaultConfig.getRenameIn()).isEqualTo(TbMsgSource.DATA); } @Test @@ -95,7 +100,7 @@ public class TbRenameKeysNodeTest { void givenMetadata_whenOnMsg_thenVerifyOutput() throws Exception { config = new TbRenameKeysNodeConfiguration().defaultConfiguration(); config.setRenameKeysMapping(Map.of("TestKey_1", "Attribute_1", "TestKey_2", "Attribute_2")); - config.setFromMetadata(true); + config.setRenameIn(TbMsgSource.METADATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -148,6 +153,20 @@ public class TbRenameKeysNodeTest { assertThat(newMsg).isSameAs(msg); } + @Test + void givenOldConfig_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception { + // GIVEN + var config = new TbRenameKeysNodeConfiguration().defaultConfiguration(); + var oldConfigJson = (ObjectNode) JacksonUtil.valueToTree(config); + oldConfigJson.remove("renameIn"); + oldConfigJson.put("fromMetadata", "false"); + // WHEN + TbPair upgrade = node.upgrade(0, oldConfigJson); + // THEN + assertTrue(upgrade.getFirst()); + assertEquals(config, JacksonUtil.treeToValue(upgrade.getSecond(), config.getClass())); + } + private TbMsg getTbMsg(EntityId entityId, String data) { final Map mdMap = Map.of( "TestKey_1", "Test", From a0cfa0479e14933a1062b192e31c887714bca78c Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 19 Sep 2023 12:39:48 +0300 Subject: [PATCH 2/8] updated docs for transformation nodes && added minor fixes && added tests tests for updated logic in to email node --- .../deduplication/TbMsgDeduplicationNode.java | 9 +- .../rule/engine/mail/TbMsgToEmailNode.java | 45 ++--- .../mail/TbMsgToEmailNodeConfiguration.java | 16 +- .../transform/TbChangeOriginatorNode.java | 16 +- .../rule/engine/transform/TbCopyKeysNode.java | 3 +- .../engine/transform/TbDeleteKeysNode.java | 2 +- .../rule/engine/transform/TbJsonPathNode.java | 5 +- .../engine/transform/TbRenameKeysNode.java | 6 +- .../engine/transform/TbSplitArrayMsgNode.java | 5 +- .../engine/transform/TbTransformMsgNode.java | 6 +- .../util/EntitiesCustomerIdAsyncLoader.java | 3 +- .../engine/mail/TbMsgToEmailNodeTest.java | 189 +++++++++++------- 12 files changed, 177 insertions(+), 128 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index 81bb3d6772..defc3fed0a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -49,11 +49,10 @@ import java.util.concurrent.TimeUnit; configClazz = TbMsgDeduplicationNodeConfiguration.class, nodeDescription = "Deduplicate messages within the same originator entity for a configurable period " + "based on a specified deduplication strategy.", - nodeDetails = "Rule node allows you to select one of the following strategy to deduplicate messages:

" + - "FIRST - return first message that arrived during deduplication period.

" + - "LAST - return last message that arrived during deduplication period.

" + - "ALL - return all messages as a single JSON array message. " + - "Where each element represents object with msg and metadata inner properties.

", + nodeDetails = "Deduplication strategy:
  • FIRST - return first message that arrived during deduplication period.
  • " + + "
  • LAST - return last message that arrived during deduplication period.
  • " + + "
  • ALL - return all messages as a single JSON array message. " + + "Where each element represents object with msg and metadata inner properties.
", icon = "content_copy", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeMsgDeduplicationConfig" diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index a52641bedc..f16d5bc666 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -15,8 +15,8 @@ */ package org.thingsboard.rule.engine.mail; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; @@ -30,9 +30,9 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; -import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -42,8 +42,11 @@ import java.util.Map; name = "to email", configClazz = TbMsgToEmailNodeConfiguration.class, nodeDescription = "Transforms message to email message", - nodeDetails = "Transforms message to email message by populating email fields using values derived from message metadata. " + - "Set 'SEND_EMAIL' output message type.", + nodeDetails = "Transforms message to email message. All email fields support templatization. " + + "Supports adding inline base64 images to the mail body from message metadata by checking for the images metadata key " + + "that should represent a map of entries: contentId to based64 image value.

" + + "Set SEND_EMAIL output message type if transformation completed successfully.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeToEmailConfig", icon = "email" @@ -54,12 +57,12 @@ public class TbMsgToEmailNode implements TbNode { private static final String DYNAMIC = "dynamic"; private TbMsgToEmailNodeConfiguration config; - private boolean isDynamicHtmlTemplate; + private boolean dynamicMailBodyType; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbMsgToEmailNodeConfiguration.class); - this.isDynamicHtmlTemplate = DYNAMIC.equals(this.config.getMailBodyType()); + this.dynamicMailBodyType = DYNAMIC.equals(this.config.getMailBodyType()); } @Override @@ -74,24 +77,22 @@ public class TbMsgToEmailNode implements TbNode { } } - private TbMsg buildEmailMsg(TbContext ctx, TbMsg msg, TbEmail email) throws JsonProcessingException { + private TbMsg buildEmailMsg(TbContext ctx, TbMsg msg, TbEmail email) { String emailJson = JacksonUtil.toString(email); return ctx.transformMsg(msg, TbMsgType.SEND_EMAIL, msg.getOriginator(), msg.getMetaData().copy(), emailJson); } - private TbEmail convert(TbMsg msg) throws IOException { + private TbEmail convert(TbMsg msg) { TbEmail.TbEmailBuilder builder = TbEmail.builder(); - builder.from(fromTemplate(this.config.getFromTemplate(), msg)); - builder.to(fromTemplate(this.config.getToTemplate(), msg)); - builder.cc(fromTemplate(this.config.getCcTemplate(), msg)); - builder.bcc(fromTemplate(this.config.getBccTemplate(), msg)); - if(isDynamicHtmlTemplate) { - builder.html(Boolean.parseBoolean(fromTemplate(this.config.getIsHtmlTemplate(), msg))); - } else { - builder.html(Boolean.parseBoolean(this.config.getMailBodyType())); - } - builder.subject(fromTemplate(this.config.getSubjectTemplate(), msg)); - builder.body(fromTemplate(this.config.getBodyTemplate(), msg)); + builder.from(fromTemplate(config.getFromTemplate(), msg)); + builder.to(fromTemplate(config.getToTemplate(), msg)); + builder.cc(fromTemplate(config.getCcTemplate(), msg)); + builder.bcc(fromTemplate(config.getBccTemplate(), msg)); + String htmlStr = dynamicMailBodyType ? + fromTemplate(config.getIsHtmlTemplate(), msg) : config.getMailBodyType(); + builder.html(Boolean.parseBoolean(htmlStr)); + builder.subject(fromTemplate(config.getSubjectTemplate(), msg)); + builder.body(fromTemplate(config.getBodyTemplate(), msg)); String imagesStr = msg.getMetaData().getValue(IMAGES); if (!StringUtils.isEmpty(imagesStr)) { Map imgMap = JacksonUtil.fromString(imagesStr, new TypeReference>() {}); @@ -101,11 +102,7 @@ public class TbMsgToEmailNode implements TbNode { } private String fromTemplate(String template, TbMsg msg) { - if (!StringUtils.isEmpty(template)) { - return TbNodeUtils.processPattern(template, msg); - } else { - return null; - } + return StringUtils.isNotEmpty(template) ? TbNodeUtils.processPattern(template, msg) : null; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java index 7b8ddefa7f..f31b5a3893 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java @@ -19,7 +19,7 @@ import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; @Data -public class TbMsgToEmailNodeConfiguration implements NodeConfiguration { +public class TbMsgToEmailNodeConfiguration implements NodeConfiguration { private String fromTemplate; private String toTemplate; @@ -28,16 +28,16 @@ public class TbMsgToEmailNodeConfiguration implements NodeConfiguration { private String subjectTemplate; private String bodyTemplate; private String isHtmlTemplate; - private String mailBodyType; + private String mailBodyType; // Plain Text -> false. HTML - true. Dynamic - value used from isHtmlTemplate. @Override public TbMsgToEmailNodeConfiguration defaultConfiguration() { - TbMsgToEmailNodeConfiguration configuration = new TbMsgToEmailNodeConfiguration(); - configuration.fromTemplate = "info@testmail.org"; - configuration.toTemplate = "${userEmail}"; - configuration.subjectTemplate = "Device ${deviceType} temperature high"; - configuration.bodyTemplate = "Device ${deviceName} has high temperature ${temp}"; - configuration.mailBodyType = "false"; + var configuration = new TbMsgToEmailNodeConfiguration(); + configuration.setFromTemplate("info@testmail.org"); + configuration.setToTemplate("${userEmail}"); + configuration.setSubjectTemplate("Device ${deviceType} temperature high"); + configuration.setBodyTemplate("Device ${deviceName} has high temperature $[temperature]"); + configuration.setMailBodyType("false"); return configuration; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index 9efc051e2b..a26cec57ee 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -43,13 +43,15 @@ import java.util.NoSuchElementException; type = ComponentType.TRANSFORMATION, name = "change originator", configClazz = TbChangeOriginatorNodeConfiguration.class, - nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity/Alarm Originator/Entity by name pattern", - nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + - "If multiple related entities are found, only first entity is used as new originator, other entities are discarded.
" + - "Alarm Originator might be found only if the original Originator is Alarm entity.
" + - "Entity by name pattern lookup only if found only in case original Originator is Alarm entity.
" + - "Lookup of an entity by name pattern requires selection of entity type and name pattern to be specified in the configuration. " + - "Allowed entity types to select: 'DEVICE', 'ASSET', 'ENTITY_VIEW', 'EDGE' and 'USER'.

" + + nodeDescription = "Change message originator to Tenant/Customer/Related Entity/Alarm Originator/Entity by name pattern.", + nodeDetails = "Configuration:
  • Tenant - use current tenant as new originator.
  • " + + "
  • Customer - use customer of incoming message originator as new originator. " + + "Only for originators with one of the following type: 'User', 'Asset', 'Device' that assigned to customers.
  • " + + "
  • Related Entity - use related entity as new originator. Lookup based on configured relation direction and relation type. " + + "If multiple related entities are found, only first entity is used as new originator, other entities are discarded.
  • " + + "
  • Alarm Originator - use alarm originator as new originator. Only if incoming message originator is alarm entity.
  • " + + "
  • Entity by name pattern - new originator lookup requires selection one of the following entity types: " + + "'Device', 'Asset', 'Entity View', 'Edge' or 'User' and input of name pattern.
" + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeChangeOriginatorConfig", diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java index 93b5ecb5d7..25467caf5a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java @@ -44,7 +44,8 @@ import java.util.stream.Collectors; configClazz = TbCopyKeysNodeConfiguration.class, nodeDescription = "Copies key-values from message to message metadata or vice-versa.", nodeDetails = "Fetches key-values from message or message metadata based on the keys list specified in the configuration " + - "and copies them into message metadata or message. Keys that are absent in the fetch source will be ignored. " + + "and copies them into message metadata or into message itself in accordance with the fetch source. " + + "Keys that are absent in the fetch source will be ignored. " + "Use regular expression(s) as a key(s) to copy keys by pattern.

" + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java index 4666016f78..15ed71b0ae 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java @@ -43,7 +43,7 @@ import java.util.stream.Collectors; name = "delete key-values", version = 1, configClazz = TbDeleteKeysNodeConfiguration.class, - nodeDescription = "Removes key-values from message or metadata.", + nodeDescription = "Removes key-values from message or message metadata.", nodeDetails = "Removes key-values from message or message metadata based on the keys list specified in the configuration. " + "Use regular expression(s) as a key(s) to remove keys by pattern.

" + "Output connections: Success, Failure.", diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java index 9454bd51c4..6e00b98e3e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java @@ -38,9 +38,8 @@ import java.util.concurrent.ExecutionException; name = "json path", configClazz = TbJsonPathNodeConfiguration.class, nodeDescription = "Transforms incoming message body using JSONPath expression.", - nodeDetails = "JSONPath expression specifies a path to an element or a set of elements in a JSON structure.
" + - "'$' represents the root object or array.
" + - "If JSONPath expression evaluation failed, incoming message routes via Failure chain.

" + + nodeDetails = "JSONPath expression specifies a path to an element or a set of elements in a JSON structure. " + + "$ represents the root object or array.

" + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, icon = "functions", diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java index d6e4e0dc34..7305c3ee78 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java @@ -39,9 +39,9 @@ import java.util.concurrent.ExecutionException; name = "rename keys", version = 1, configClazz = TbRenameKeysNodeConfiguration.class, - nodeDescription = "Renames message or message metadata key names to the new key names selected in the key mapping.", - nodeDetails = "If the key name that is selected in the key mapping is missed in the " + - "selected source(message or message metadata), it will be ignored.

" + + nodeDescription = "Renames message or message metadata key names.", + nodeDetails = "Renames key names in the message or message metadata based on the provided key names mapping. " + + "If key to rename doesn't exists in the specified source(message or message metadata) it will be ignored.

" + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeRenameKeysConfig", diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java index 7a3e53c69c..7a9c812ebc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java @@ -40,9 +40,8 @@ import java.util.concurrent.ExecutionException; name = "split array msg", configClazz = EmptyNodeConfiguration.class, nodeDescription = "Split array message into several messages", - nodeDetails = "Split the array fetched from the message. If the msg data is not a JSON array returns the " - + "incoming message as outbound message with Failure chain, otherwise returns " - + "inner objects of the extracted array as separate messages via Success chain.

" + + nodeDetails = "Split array message and returns inner objects as separate messages. " + + "Message metadata and type for each output message will be taken from original message.

" + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, icon = "content_copy", diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index 0cff412d39..79549edbd7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -34,9 +34,9 @@ import java.util.List; configClazz = TbTransformMsgNodeConfiguration.class, nodeDescription = "Change Message payload, Metadata or Message type using JavaScript", nodeDetails = "JavaScript function receive 3 input parameters
" + - "metadata - is a Message metadata.
" + - "msg - is a Message payload.
" + - "msgType - is a Message type.
" + + "msg - is a message payload.
" + + "metadata - is a message metadata.
" + + "msgType - is a message type.
" + "Should return the following structure:
" + "{ msg: new payload,
   metadata: new metadata,
   msgType: new msgType }

" + "All fields in resulting object are optional and will be taken from original message if not specified.

" + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java index 9579c73058..5a19fa860f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java @@ -44,8 +44,7 @@ public class EntitiesCustomerIdAsyncLoader { } private static ListenableFuture toCustomerIdAsync(TbContext ctx, ListenableFuture future) { - return Futures.transformAsync(future, in -> in != null ? Futures.immediateFuture(in.getCustomerId()) - : Futures.immediateFuture(null), ctx.getDbCallbackExecutor()); + return Futures.transform(future, in -> in != null ? in.getCustomerId() : null, ctx.getDbCallbackExecutor()); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java index bf4d090c1c..4f1b0488d7 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java @@ -15,12 +15,15 @@ */ package org.thingsboard.rule.engine.mail; -import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.junit.Test; -import org.junit.runner.RunWith; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbEmail; @@ -28,85 +31,135 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; 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.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import java.io.IOException; +import java.util.UUID; +import java.util.stream.Stream; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -@RunWith(MockitoJUnitRunner.class) public class TbMsgToEmailNodeTest { - private TbMsgToEmailNode emailNode; + private static final int EXPECTED_TEMPERATURE = 30; + private static final String EXPECTED_DEVICE_NAME = "TH-001"; + private static final String EXPECTED_DEVICE_TYPE = "thermostat"; + private static final String EXPECTED_SUBJECT = "Device " + EXPECTED_DEVICE_TYPE + " temperature high"; + private static final String EXPECTED_BODY = "Device " + EXPECTED_DEVICE_NAME + " has high temperature " + EXPECTED_TEMPERATURE; + private static final String EXPECTED_TO_EMAIL = "user@email.io"; + private static final String DYNAMIC_MAIL_BODY_TYPE = "dynamic"; + + private EntityId originator; + private TbMsgToEmailNode node; + private TbMsgToEmailNodeConfiguration config; + + private TbContext ctxMock; + + @BeforeEach + void setUp() throws TbNodeException { + ctxMock = mock(TbContext.class); + originator = new DeviceId(UUID.randomUUID()); + config = new TbMsgToEmailNodeConfiguration().defaultConfiguration(); + node = new TbMsgToEmailNode(); + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + } - @Mock - private TbContext ctx; + @AfterEach + void tearDown() { + node.destroy(); + } - private EntityId originator = new DeviceId(Uuids.timeBased()); - private TbMsgMetaData metaData = new TbMsgMetaData(); - private String rawJson = "{\"name\": \"temp\", \"passed\": 5 , \"complex\": {\"val\":12, \"count\":100}}"; + @Test + void givenDefaultConfig_whenVerify_thenOK() { + // GIVEN-WHEN-THEN + assertThat(config.getFromTemplate()).isEqualTo("info@testmail.org"); + assertThat(config.getToTemplate()).isEqualTo("${userEmail}"); + assertThat(config.getSubjectTemplate()).isEqualTo("Device ${deviceType} temperature high"); + assertThat(config.getBodyTemplate()).isEqualTo("Device ${deviceName} has high temperature $[temperature]"); + } - private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); - private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); + static Stream MailBodyTypeTestConfig() { + return Stream.of( + new MailBodyTypeTestConfig(false, "false", null), + new MailBodyTypeTestConfig(false, null, null), + new MailBodyTypeTestConfig(false, DYNAMIC_MAIL_BODY_TYPE, "false"), + new MailBodyTypeTestConfig(true, DYNAMIC_MAIL_BODY_TYPE, "true"), + new MailBodyTypeTestConfig(true, "true", null) + ); + } - @Test - public void msgCanBeConverted() { - initWithScript(); - metaData.putValue("username", "oreo"); - metaData.putValue("userEmail", "user@email.io"); - metaData.putValue("name", "temp"); - metaData.putValue("passed", "5"); - metaData.putValue("count", "100"); - TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); - - emailNode.onMsg(ctx, msg); - - ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); - ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); - ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); - ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); - verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - - - assertEquals(TbMsgType.SEND_EMAIL, typeCaptor.getValue()); - assertEquals(originator, originatorCaptor.getValue()); - assertEquals("oreo", metadataCaptor.getValue().getValue("username")); - assertNotSame(metaData, metadataCaptor.getValue()); - - TbEmail actual = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), TbEmail.class); - - TbEmail expected = TbEmail.builder() - .from("test@mail.org") - .to("user@email.io") - .subject("Hi oreo there") - .body("temp is to high. Current 5 and 100") + @ParameterizedTest + @MethodSource("MailBodyTypeTestConfig") + public void givenMailBodyTypeTestConfig_whenOnMsg_thenVerify(MailBodyTypeTestConfig testConfig) throws TbNodeException { + // GIVEN + String mailBodyType = testConfig.getMailBodyType(); + config.setMailBodyType(mailBodyType); + if (DYNAMIC_MAIL_BODY_TYPE.equals(mailBodyType)) { + config.setIsHtmlTemplate("${html}"); + } + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + var md = new TbMsgMetaData(); + md.putValue("userEmail", EXPECTED_TO_EMAIL); + md.putValue("deviceType", EXPECTED_DEVICE_TYPE); + md.putValue("deviceName", EXPECTED_DEVICE_NAME); + if (testConfig.getMdValue() != null) { + md.putValue("html", testConfig.getMdValue()); + } + + var msgDataStr = "{\"temperature\": " + EXPECTED_TEMPERATURE + "}"; + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, md, msgDataStr); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + var typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); + var originatorCaptor = ArgumentCaptor.forClass(EntityId.class); + var metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); + var dataCaptor = ArgumentCaptor.forClass(String.class); + verify(ctxMock, times(1)).transformMsg( + msgCaptor.capture(), + typeCaptor.capture(), + originatorCaptor.capture(), + metadataCaptor.capture(), + dataCaptor.capture() + ); + verify(ctxMock, never()).tellFailure(any(), any()); + + Assertions.assertEquals(TbMsgType.SEND_EMAIL, typeCaptor.getValue()); + Assertions.assertEquals(originator, originatorCaptor.getValue()); + Assertions.assertNotSame(md, metadataCaptor.getValue()); + + var actual = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), TbEmail.class); + var expected = getExpectedTbEmail(testConfig.isExpectedHtmlValue()); + + Assertions.assertEquals(expected, actual); + } + + private TbEmail getExpectedTbEmail(boolean html) { + return TbEmail.builder() + .from(config.getFromTemplate()) + .to(EXPECTED_TO_EMAIL) + .subject(EXPECTED_SUBJECT) + .body(EXPECTED_BODY) + .html(html) .build(); - assertEquals(expected, actual); } - private void initWithScript() { - try { - TbMsgToEmailNodeConfiguration config = new TbMsgToEmailNodeConfiguration(); - config.setFromTemplate("test@mail.org"); - config.setToTemplate("${userEmail}"); - config.setSubjectTemplate("Hi ${username} there"); - config.setBodyTemplate("${name} is to high. Current ${passed} and ${count}"); - config.setMailBodyType("false"); - TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - - emailNode = new TbMsgToEmailNode(); - emailNode.init(ctx, nodeConfiguration); - } catch (TbNodeException ex) { - throw new IllegalStateException(ex); - } + @Data + @RequiredArgsConstructor + static class MailBodyTypeTestConfig { + private final boolean expectedHtmlValue; + private final String mailBodyType; + private final String mdValue; } } From 8b88f7bacfa395aa142808a4f38362fc1a233ed5 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 21 Sep 2023 12:50:01 +0300 Subject: [PATCH 3/8] minor refactoring after self review --- .../deduplication/TbMsgDeduplicationNode.java | 4 +-- .../rule/engine/mail/TbMsgToEmailNode.java | 12 +++++---- .../transform/TbChangeOriginatorNode.java | 8 +++--- .../rule/engine/transform/TbCopyKeysNode.java | 6 ++--- .../engine/transform/TbDeleteKeysNode.java | 6 ++--- .../engine/mail/TbMsgToEmailNodeTest.java | 26 +++++++++---------- 6 files changed, 32 insertions(+), 30 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java index defc3fed0a..043cc7793f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/deduplication/TbMsgDeduplicationNode.java @@ -49,10 +49,10 @@ import java.util.concurrent.TimeUnit; configClazz = TbMsgDeduplicationNodeConfiguration.class, nodeDescription = "Deduplicate messages within the same originator entity for a configurable period " + "based on a specified deduplication strategy.", - nodeDetails = "Deduplication strategy:
  • FIRST - return first message that arrived during deduplication period.
  • " + + nodeDetails = "Deduplication strategies:
    • FIRST - return first message that arrived during deduplication period.
    • " + "
    • LAST - return last message that arrived during deduplication period.
    • " + "
    • ALL - return all messages as a single JSON array message. " + - "Where each element represents object with msg and metadata inner properties.
    ", + "Where each element represents object with msg and metadata inner properties.
", icon = "content_copy", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeMsgDeduplicationConfig" diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index f16d5bc666..cb7b7d118b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -16,7 +16,6 @@ package org.thingsboard.rule.engine.mail; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; @@ -30,7 +29,6 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; -import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import java.util.HashMap; @@ -42,9 +40,13 @@ import java.util.Map; name = "to email", configClazz = TbMsgToEmailNodeConfiguration.class, nodeDescription = "Transforms message to email message", - nodeDetails = "Transforms message to email message. All email fields support templatization. " + - "Supports adding inline base64 images to the mail body from message metadata by checking for the images metadata key " + - "that should represent a map of entries: contentId to based64 image value.

" + + nodeDetails = "Transforms message to email message. All email fields support templatization.

" + + "Additionally, supports adding inline base64 images to the mail body from message metadata " + + "by extracting them from images metadata key-value.
" + + "images key-value should represent a map of entries: cid --> encodedImage.
" + + "E.g., cid is a tb.example.png and
" + + "encodedImage is a data:image/png;base64,..., " + + "then you can use img HTML tag with src: cid:tb.example.png.

" + "Set SEND_EMAIL output message type if transformation completed successfully.

" + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index a26cec57ee..a88a87b400 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -44,10 +44,10 @@ import java.util.NoSuchElementException; name = "change originator", configClazz = TbChangeOriginatorNodeConfiguration.class, nodeDescription = "Change message originator to Tenant/Customer/Related Entity/Alarm Originator/Entity by name pattern.", - nodeDetails = "Configuration: " + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeChangeOriginatorConfig", diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java index 02f031412b..aca70b0ebc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.msg.TbMsg; import java.util.Arrays; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.regex.Pattern; @@ -43,7 +44,7 @@ import java.util.stream.Collectors; version = 1, configClazz = TbCopyKeysNodeConfiguration.class, nodeDescription = "Copies key-value pairs from message to message metadata or vice-versa.", - nodeDetails = "Copies key-value pairs from the message body to metadata, or vice-versa, according to the configured direction and keys. " + + nodeDetails = "Copies key-value pairs from the message to message metadata, or vice-versa, according to the configured direction and keys. " + "Regular expressions can be used to define which keys-value pairs to copy. Any configured key not found in the source will be ignored.

" + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, @@ -54,6 +55,7 @@ public class TbCopyKeysNode extends TbAbstractTransformNodeWithTbMsgSource { private TbCopyKeysNodeConfiguration config; private TbMsgSource copyFrom; + private List compiledKeyPatterns; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java index 6899ac3fad..7bb63ee1bd 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java @@ -31,6 +31,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.ExecutionException; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -42,7 +43,7 @@ import java.util.stream.Collectors; version = 1, configClazz = TbDeleteKeysNodeConfiguration.class, nodeDescription = "Deletes key-value pairs from message or message metadata.", - nodeDetails = "Deletes key-value pairs from the message body or metadata according to the configured " + + nodeDetails = "Deletes key-value pairs from the message or message metadata according to the configured " + "keys and/or regular expressions.

" + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, @@ -53,6 +54,7 @@ public class TbDeleteKeysNode extends TbAbstractTransformNodeWithTbMsgSource { private TbDeleteKeysNodeConfiguration config; private TbMsgSource deleteFrom; + private List compiledKeyPatterns; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java index 9170016d06..705ce3fc42 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java @@ -41,7 +41,7 @@ import java.util.concurrent.ExecutionException; configClazz = TbRenameKeysNodeConfiguration.class, nodeDescription = "Renames message or message metadata keys.", nodeDetails = "Renames keys in the message or message metadata according to the provided mapping. " + - "If key to rename doesn't exists in the specified source (message or message metadata) it will be ignored.

" + + "If key to rename doesn't exist in the specified source (message or message metadata) it will be ignored.

" + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeRenameKeysConfig", diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java index bf88aa81a2..5f9efed3fb 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java @@ -44,12 +44,9 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.BDDMockito.willCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; public class TbCopyKeysNodeTest { @@ -90,7 +87,7 @@ public class TbCopyKeysNodeTest { node.onMsg(ctx, getTbMsg(deviceId, TbMsg.EMPTY_JSON_OBJECT)); ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); + verify(ctx).tellSuccess(newMsgCaptor.capture()); verify(ctx, never()).tellFailure(any(), any()); TbMsg newMsg = newMsgCaptor.getValue(); @@ -113,7 +110,7 @@ public class TbCopyKeysNodeTest { node.onMsg(ctx, getTbMsg(deviceId, data)); ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); + verify(ctx).tellSuccess(newMsgCaptor.capture()); verify(ctx, never()).tellFailure(any(), any()); TbMsg newMsg = newMsgCaptor.getValue(); @@ -140,7 +137,7 @@ public class TbCopyKeysNodeTest { node.onMsg(ctx, msg); ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); + verify(ctx).tellSuccess(newMsgCaptor.capture()); verify(ctx, never()).tellFailure(any(), any()); TbMsg newMsg = newMsgCaptor.getValue(); @@ -155,7 +152,7 @@ public class TbCopyKeysNodeTest { node.onMsg(ctx, msg); ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); + verify(ctx).tellSuccess(newMsgCaptor.capture()); verify(ctx, never()).tellFailure(any(), any()); TbMsg newMsg = newMsgCaptor.getValue(); @@ -166,7 +163,8 @@ public class TbCopyKeysNodeTest { private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyUpgradeResultAndConfig() { return Stream.of( - Arguments.of(0, "{\"fromMetadata\":false,\"keys\":[\"temperature\"]}", true, "{\"copyFrom\":\"DATA\",\"keys\":[\"temperature\"]}") + Arguments.of(0, "{\"fromMetadata\":false,\"keys\":[\"temperature\"]}", true, "{\"copyFrom\":\"DATA\",\"keys\":[\"temperature\"]}"), + Arguments.of(0, "{\"fromMetadata\":true,\"keys\":[\"temperature\"]}", true, "{\"copyFrom\":\"METADATA\",\"keys\":[\"temperature\"]}") ); } @@ -175,7 +173,6 @@ public class TbCopyKeysNodeTest { void givenFromVersionAndConfig_whenUpgrade_thenVerifyUpgradeResultAndConfig(int givenVersion, String givenConfigStr, boolean hasChanges, String expectedConfigStr) throws Exception { // GIVEN - willCallRealMethod().given(node).upgrade(anyInt(), any()); JsonNode givenConfig = JacksonUtil.toJsonNode(givenConfigStr); JsonNode expectedConfig = JacksonUtil.toJsonNode(expectedConfigStr); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java index eaa3c11e7e..176979a546 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java @@ -44,8 +44,6 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.BDDMockito.willCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -142,7 +140,8 @@ public class TbDeleteKeysNodeTest { private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyUpgradeResultAndConfig() { return Stream.of( - Arguments.of(0, "{\"fromMetadata\":false,\"keys\":[\"temperature\"]}", true, "{\"deleteFrom\":\"DATA\",\"keys\":[\"temperature\"]}") + Arguments.of(0, "{\"fromMetadata\":false,\"keys\":[\"temperature\"]}", true, "{\"deleteFrom\":\"DATA\",\"keys\":[\"temperature\"]}"), + Arguments.of(0, "{\"fromMetadata\":true,\"keys\":[\"temperature\"]}", true, "{\"deleteFrom\":\"METADATA\",\"keys\":[\"temperature\"]}") ); } @@ -151,7 +150,6 @@ public class TbDeleteKeysNodeTest { void givenFromVersionAndConfig_whenUpgrade_thenVerifyUpgradeResultAndConfig(int givenVersion, String givenConfigStr, boolean hasChanges, String expectedConfigStr) throws Exception { // GIVEN - willCallRealMethod().given(node).upgrade(anyInt(), any()); JsonNode givenConfig = JacksonUtil.toJsonNode(givenConfigStr); JsonNode expectedConfig = JacksonUtil.toJsonNode(expectedConfigStr); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java index 84c0bc8ba9..ea46db2e2b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java @@ -42,8 +42,6 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.BDDMockito.willCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -158,7 +156,8 @@ public class TbRenameKeysNodeTest { private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyUpgradeResultAndConfig() { return Stream.of( - Arguments.of(0, "{\"fromMetadata\":false,\"renameKeysMapping\":{\"temp\":\"temperature\"}}", true, "{\"renameIn\":\"DATA\",\"renameKeysMapping\":{\"temp\":\"temperature\"}}") + Arguments.of(0, "{\"fromMetadata\":false,\"renameKeysMapping\":{\"temp\":\"temperature\"}}", true, "{\"renameIn\":\"DATA\",\"renameKeysMapping\":{\"temp\":\"temperature\"}}"), + Arguments.of(0, "{\"fromMetadata\":true,\"renameKeysMapping\":{\"temp\":\"temperature\"}}", true, "{\"renameIn\":\"METADATA\",\"renameKeysMapping\":{\"temp\":\"temperature\"}}") ); } @@ -167,7 +166,6 @@ public class TbRenameKeysNodeTest { void givenFromVersionAndConfig_whenUpgrade_thenVerifyUpgradeResultAndConfig(int givenVersion, String givenConfigStr, boolean hasChanges, String expectedConfigStr) throws Exception { // GIVEN - willCallRealMethod().given(node).upgrade(anyInt(), any()); JsonNode givenConfig = JacksonUtil.toJsonNode(givenConfigStr); JsonNode expectedConfig = JacksonUtil.toJsonNode(expectedConfigStr); From f2955621cc56c3972f517492829a2efe436b7f5d Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 30 Nov 2023 16:21:49 +0200 Subject: [PATCH 8/8] removed resolved todo --- .../org/thingsboard/rule/engine/transform/TbCopyKeysNode.java | 1 - 1 file changed, 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java index aca70b0ebc..ef950ff563 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java @@ -36,7 +36,6 @@ import java.util.concurrent.ExecutionException; import java.util.regex.Pattern; import java.util.stream.Collectors; -// todo: check UI for copy key-value pairs @Slf4j @RuleNode( type = ComponentType.TRANSFORMATION,