Filter nodes UI configuration.
This commit is contained in:
		
							parent
							
								
									fd1199ee1c
								
							
						
					
					
						commit
						784de0836f
					
				@ -35,7 +35,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
 | 
			
		||||
        nodeDetails = "Evaluate incoming Message with configured JS condition. " +
 | 
			
		||||
                "If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." +
 | 
			
		||||
                "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code>" +
 | 
			
		||||
                "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>",
 | 
			
		||||
                "Message metadata can be accessed via <code>metadata</code> property. For example <code>metadata.customerName === 'John';</code>",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbFilterNodeScriptConfig")
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,7 @@ public class TbJsFilterNode implements TbNode {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
 | 
			
		||||
        this.config = TbNodeUtils.convert(configuration, TbJsFilterNodeConfiguration.class);
 | 
			
		||||
        this.jsEngine = new NashornJsEngine(config.getJsScript());
 | 
			
		||||
        this.jsEngine = new NashornJsEngine(config.getJsScript(), "Filter");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@ public class TbJsFilterNodeConfiguration implements NodeConfiguration {
 | 
			
		||||
    @Override
 | 
			
		||||
    public TbJsFilterNodeConfiguration defaultConfiguration() {
 | 
			
		||||
        TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration();
 | 
			
		||||
        configuration.setJsScript("msg.passed < 15 && msg.name === 'Vit' && meta.temp == 10 && msg.bigObj.prop == 42;");
 | 
			
		||||
        configuration.setJsScript("return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 10 && msg.bigObj.prop == 42;");
 | 
			
		||||
        return configuration;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,9 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
 | 
			
		||||
        nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " +
 | 
			
		||||
                "If Array is empty - message not routed to next Node. " +
 | 
			
		||||
                "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code> " +
 | 
			
		||||
                "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>")
 | 
			
		||||
                "Message metadata can be accessed via <code>metadata</code> property. For example <code>metadata.customerName === 'John';</code>",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbFilterNodeSwitchConfig")
 | 
			
		||||
public class TbJsSwitchNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
    private TbJsSwitchNodeConfiguration config;
 | 
			
		||||
@ -45,22 +47,11 @@ public class TbJsSwitchNode implements TbNode {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
 | 
			
		||||
        this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class);
 | 
			
		||||
        if (config.getAllowedRelations().size() < 1) {
 | 
			
		||||
            String message = "Switch node should have at least 1 relation";
 | 
			
		||||
            log.error(message);
 | 
			
		||||
            throw new IllegalStateException(message);
 | 
			
		||||
        }
 | 
			
		||||
        if (!config.isRouteToAllWithNoCheck()) {
 | 
			
		||||
            this.jsEngine = new NashornJsEngine(config.getJsScript());
 | 
			
		||||
        }
 | 
			
		||||
        this.jsEngine = new NashornJsEngine(config.getJsScript(), "Switch");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onMsg(TbContext ctx, TbMsg msg) {
 | 
			
		||||
        if (config.isRouteToAllWithNoCheck()) {
 | 
			
		||||
            ctx.tellNext(msg, config.getAllowedRelations());
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        ListeningExecutor jsExecutor = ctx.getJsExecutor();
 | 
			
		||||
        withCallback(jsExecutor.executeAsync(() -> jsEngine.executeSwitch(toBindings(msg))),
 | 
			
		||||
                result -> processSwitch(ctx, msg, result),
 | 
			
		||||
@ -68,15 +59,7 @@ public class TbJsSwitchNode implements TbNode {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void processSwitch(TbContext ctx, TbMsg msg, Set<String> nextRelations) {
 | 
			
		||||
        if (validateRelations(nextRelations)) {
 | 
			
		||||
            ctx.tellNext(msg, nextRelations);
 | 
			
		||||
        } else {
 | 
			
		||||
            ctx.tellError(msg, new IllegalStateException("Unsupported relation for switch " + nextRelations));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean validateRelations(Set<String> nextRelations) {
 | 
			
		||||
        return config.getAllowedRelations().containsAll(nextRelations);
 | 
			
		||||
        ctx.tellNext(msg, nextRelations);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Bindings toBindings(TbMsg msg) {
 | 
			
		||||
 | 
			
		||||
@ -25,19 +25,15 @@ import java.util.Set;
 | 
			
		||||
public class TbJsSwitchNodeConfiguration implements NodeConfiguration {
 | 
			
		||||
 | 
			
		||||
    private String jsScript;
 | 
			
		||||
    private Set<String> allowedRelations;
 | 
			
		||||
    private boolean routeToAllWithNoCheck;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public TbJsSwitchNodeConfiguration defaultConfiguration() {
 | 
			
		||||
        TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration();
 | 
			
		||||
        configuration.setJsScript("function nextRelation(meta, msg) {\n" +
 | 
			
		||||
        configuration.setJsScript("function nextRelation(metadata, msg) {\n" +
 | 
			
		||||
                "    return ['one','nine'];" +
 | 
			
		||||
                "};\n" +
 | 
			
		||||
                "\n" +
 | 
			
		||||
                "nextRelation(meta, msg);");
 | 
			
		||||
        configuration.setAllowedRelations(Sets.newHashSet("one", "two"));
 | 
			
		||||
        configuration.setRouteToAllWithNoCheck(false);
 | 
			
		||||
                "return nextRelation(metadata, msg);");
 | 
			
		||||
        return configuration;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,9 @@ import org.thingsboard.server.common.msg.TbMsg;
 | 
			
		||||
        configClazz = TbMsgTypeFilterNodeConfiguration.class,
 | 
			
		||||
        nodeDescription = "Filter incoming messages by Message Type",
 | 
			
		||||
        nodeDetails = "Evaluate incoming Message with configured JS condition. " +
 | 
			
		||||
                "If incoming MessageType is expected - send Message via <b>Success</b> chain, otherwise <b>Failure</b> chain is used.")
 | 
			
		||||
                "If incoming MessageType is expected - send Message via <b>Success</b> chain, otherwise <b>Failure</b> chain is used.",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
 | 
			
		||||
        configDirective = "tbFilterNodeMessageTypeConfig")
 | 
			
		||||
public class TbMsgTypeFilterNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
    TbMsgTypeFilterNodeConfiguration config;
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@ public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration {
 | 
			
		||||
    @Override
 | 
			
		||||
    public TbMsgTypeFilterNodeConfiguration defaultConfiguration() {
 | 
			
		||||
        TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration();
 | 
			
		||||
        configuration.setMessageTypes(Arrays.asList("GET_ATTRIBUTES","POST_ATTRIBUTES","POST_TELEMETRY","RPC_REQUEST"));
 | 
			
		||||
        configuration.setMessageTypes(Arrays.asList("POST_ATTRIBUTES","POST_TELEMETRY","RPC_REQUEST"));
 | 
			
		||||
        return configuration;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -34,14 +34,20 @@ import java.util.Set;
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class NashornJsEngine {
 | 
			
		||||
 | 
			
		||||
    public static final String METADATA = "meta";
 | 
			
		||||
    public static final String METADATA = "metadata";
 | 
			
		||||
    public static final String DATA = "msg";
 | 
			
		||||
 | 
			
		||||
    private static final String JS_WRAPPER_PREFIX_TEMPLATE = "function %s(msg, metadata) { ";
 | 
			
		||||
    private static final String JS_WRAPPER_SUFFIX_TEMPLATE = "}\n %s(msg, metadata);";
 | 
			
		||||
 | 
			
		||||
    private static NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
 | 
			
		||||
 | 
			
		||||
    private CompiledScript engine;
 | 
			
		||||
 | 
			
		||||
    public NashornJsEngine(String script) {
 | 
			
		||||
        engine = compileScript(script);
 | 
			
		||||
    public NashornJsEngine(String script, String functionName) {
 | 
			
		||||
        String jsWrapperPrefix = String.format(JS_WRAPPER_PREFIX_TEMPLATE, functionName);
 | 
			
		||||
        String jsWrapperSuffix = String.format(JS_WRAPPER_SUFFIX_TEMPLATE, functionName);
 | 
			
		||||
        engine = compileScript(jsWrapperPrefix + script + jsWrapperSuffix);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static CompiledScript compileScript(String script) {
 | 
			
		||||
@ -58,15 +64,15 @@ public class NashornJsEngine {
 | 
			
		||||
    public static Bindings bindMsg(TbMsg msg) {
 | 
			
		||||
        try {
 | 
			
		||||
            Bindings bindings = new SimpleBindings();
 | 
			
		||||
            bindings.put(METADATA, msg.getMetaData().getData());
 | 
			
		||||
 | 
			
		||||
            if (ArrayUtils.isNotEmpty(msg.getData())) {
 | 
			
		||||
                ObjectMapper mapper = new ObjectMapper();
 | 
			
		||||
                JsonNode jsonNode = mapper.readTree(msg.getData());
 | 
			
		||||
                Map map = mapper.treeToValue(jsonNode, Map.class);
 | 
			
		||||
                bindings.put(DATA, map);
 | 
			
		||||
            } else {
 | 
			
		||||
                bindings.put(DATA, Collections.emptyMap());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bindings.put(METADATA, msg.getMetaData().getData());
 | 
			
		||||
            return bindings;
 | 
			
		||||
        } catch (Throwable th) {
 | 
			
		||||
            throw new IllegalArgumentException("Cannot bind js args", th);
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,7 @@ import static org.thingsboard.server.common.data.DataConstants.*;
 | 
			
		||||
          nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata",
 | 
			
		||||
          nodeDetails = "If Attributes enrichment configured, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " +
 | 
			
		||||
                "with specific prefix: <i>cs/shared/ss</i>. To access those attributes in other nodes this template can be used " +
 | 
			
		||||
                "<code>meta.cs.temperature</code> or <code>meta.shared.limit</code> " +
 | 
			
		||||
                "<code>metadata.cs.temperature</code> or <code>metadata.shared.limit</code> " +
 | 
			
		||||
                "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.")
 | 
			
		||||
public class TbGetAttributesNode implements TbNode {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
 | 
			
		||||
        nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata",
 | 
			
		||||
        nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
 | 
			
		||||
                "To access those attributes in other nodes this template can be used " +
 | 
			
		||||
                "<code>meta.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
 | 
			
		||||
                "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
 | 
			
		||||
public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
 | 
			
		||||
                "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " +
 | 
			
		||||
                "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
 | 
			
		||||
                "To access those attributes in other nodes this template can be used " +
 | 
			
		||||
                "<code>meta.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
 | 
			
		||||
                "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
 | 
			
		||||
public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> {
 | 
			
		||||
 | 
			
		||||
    private TbGetRelatedAttrNodeConfiguration config;
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
 | 
			
		||||
        nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata",
 | 
			
		||||
        nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
 | 
			
		||||
                "To access those attributes in other nodes this template can be used " +
 | 
			
		||||
                "<code>meta.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
 | 
			
		||||
                "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
 | 
			
		||||
public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 | 
			
		||||
@ -30,7 +30,7 @@ import javax.script.Bindings;
 | 
			
		||||
        configClazz = TbTransformMsgNodeConfiguration.class,
 | 
			
		||||
        nodeDescription = "Change Message payload and Metadata using JavaScript",
 | 
			
		||||
        nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside.<br/> " +
 | 
			
		||||
                "<code>meta</code> - is a Message metadata.<br/>" +
 | 
			
		||||
                "<code>metadata</code> - is a Message metadata.<br/>" +
 | 
			
		||||
                "<code>msg</code> - is a Message payload.<br/>Any properties can be changed/removed/added in those objects.")
 | 
			
		||||
public class TbTransformMsgNode extends TbAbstractTransformNode {
 | 
			
		||||
 | 
			
		||||
@ -40,7 +40,7 @@ public class TbTransformMsgNode extends TbAbstractTransformNode {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
 | 
			
		||||
        this.config = TbNodeUtils.convert(configuration, TbTransformMsgNodeConfiguration.class);
 | 
			
		||||
        this.jsEngine = new NashornJsEngine(config.getJsScript());
 | 
			
		||||
        this.jsEngine = new NashornJsEngine(config.getJsScript(), "Transform");
 | 
			
		||||
        setConfig(config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguratio
 | 
			
		||||
    public TbTransformMsgNodeConfiguration defaultConfiguration() {
 | 
			
		||||
        TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration();
 | 
			
		||||
        configuration.setStartNewChain(false);
 | 
			
		||||
        configuration.setJsScript("msg.passed = msg.passed * meta.temp; msg.bigObj.newProp = 'Ukraine' ");
 | 
			
		||||
        configuration.setJsScript("return msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine' ");
 | 
			
		||||
        return configuration;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,2 @@
 | 
			
		||||
.tb-message-type-autocomplete .tb-not-found{display:block;line-height:1.5;height:48px}.tb-message-type-autocomplete .tb-not-found .tb-no-entries{line-height:48px}.tb-message-type-autocomplete li{height:auto!important;white-space:normal!important}
 | 
			
		||||
/*# sourceMappingURL=rulenode-core-config.css.map*/
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -51,7 +51,7 @@ public class TbJsFilterNodeTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void falseEvaluationDoNotSendMsg() throws TbNodeException {
 | 
			
		||||
        initWithScript("10 > 15;");
 | 
			
		||||
        initWithScript("return 10 > 15;");
 | 
			
		||||
        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes());
 | 
			
		||||
 | 
			
		||||
        mockJsExecutor();
 | 
			
		||||
@ -64,7 +64,7 @@ public class TbJsFilterNodeTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void notValidMsgDataThrowsException() throws TbNodeException {
 | 
			
		||||
        initWithScript("10 > 15;");
 | 
			
		||||
        initWithScript("return 10 > 15;");
 | 
			
		||||
        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), new byte[4]);
 | 
			
		||||
 | 
			
		||||
        when(ctx.getJsExecutor()).thenReturn(executor);
 | 
			
		||||
@ -77,7 +77,7 @@ public class TbJsFilterNodeTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void exceptionInJsThrowsException() throws TbNodeException {
 | 
			
		||||
        initWithScript("meta.temp.curr < 15;");
 | 
			
		||||
        initWithScript("return metadata.temp.curr < 15;");
 | 
			
		||||
        TbMsgMetaData metaData = new TbMsgMetaData();
 | 
			
		||||
        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes());
 | 
			
		||||
        mockJsExecutor();
 | 
			
		||||
@ -89,12 +89,12 @@ public class TbJsFilterNodeTest {
 | 
			
		||||
 | 
			
		||||
    @Test(expected = IllegalArgumentException.class)
 | 
			
		||||
    public void notValidScriptThrowsException() throws TbNodeException {
 | 
			
		||||
        initWithScript("10 > 15 asdq out");
 | 
			
		||||
        initWithScript("return 10 > 15 asdq out");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void metadataConditionCanBeFalse() throws TbNodeException {
 | 
			
		||||
        initWithScript("meta.humidity < 15;");
 | 
			
		||||
        initWithScript("return metadata.humidity < 15;");
 | 
			
		||||
        TbMsgMetaData metaData = new TbMsgMetaData();
 | 
			
		||||
        metaData.putValue("temp", "10");
 | 
			
		||||
        metaData.putValue("humidity", "99");
 | 
			
		||||
@ -109,7 +109,7 @@ public class TbJsFilterNodeTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void metadataConditionCanBeTrue() throws TbNodeException {
 | 
			
		||||
        initWithScript("meta.temp < 15;");
 | 
			
		||||
        initWithScript("return metadata.temp < 15;");
 | 
			
		||||
        TbMsgMetaData metaData = new TbMsgMetaData();
 | 
			
		||||
        metaData.putValue("temp", "10");
 | 
			
		||||
        metaData.putValue("humidity", "99");
 | 
			
		||||
@ -123,7 +123,7 @@ public class TbJsFilterNodeTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void msgJsonParsedAndBinded() throws TbNodeException {
 | 
			
		||||
        initWithScript("msg.passed < 15 && msg.name === 'Vit' && meta.temp == 10 && msg.bigObj.prop == 42;");
 | 
			
		||||
        initWithScript("return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 10 && msg.bigObj.prop == 42;");
 | 
			
		||||
        TbMsgMetaData metaData = new TbMsgMetaData();
 | 
			
		||||
        metaData.putValue("temp", "10");
 | 
			
		||||
        metaData.putValue("humidity", "99");
 | 
			
		||||
 | 
			
		||||
@ -52,28 +52,17 @@ public class TbJsSwitchNodeTest {
 | 
			
		||||
    @Mock
 | 
			
		||||
    private ListeningExecutor executor;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void routeToAllDoNotEvaluatesJs() throws TbNodeException {
 | 
			
		||||
        HashSet<String> relations = Sets.newHashSet("one", "two");
 | 
			
		||||
        initWithScript("test qwerty", relations, true);
 | 
			
		||||
        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes());
 | 
			
		||||
 | 
			
		||||
        node.onMsg(ctx, msg);
 | 
			
		||||
        verify(ctx).tellNext(msg, relations);
 | 
			
		||||
        verifyNoMoreInteractions(ctx, executor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void multipleRoutesAreAllowed() throws TbNodeException {
 | 
			
		||||
        String jsCode = "function nextRelation(meta, msg) {\n" +
 | 
			
		||||
                "    if(msg.passed == 5 && meta.temp == 10)\n" +
 | 
			
		||||
        String jsCode = "function nextRelation(metadata, msg) {\n" +
 | 
			
		||||
                "    if(msg.passed == 5 && metadata.temp == 10)\n" +
 | 
			
		||||
                "        return ['three', 'one']\n" +
 | 
			
		||||
                "    else\n" +
 | 
			
		||||
                "        return 'two';\n" +
 | 
			
		||||
                "};\n" +
 | 
			
		||||
                "\n" +
 | 
			
		||||
                "nextRelation(meta, msg);";
 | 
			
		||||
        initWithScript(jsCode, Sets.newHashSet("one", "two", "three"), false);
 | 
			
		||||
                "return nextRelation(metadata, msg);";
 | 
			
		||||
        initWithScript(jsCode);
 | 
			
		||||
        TbMsgMetaData metaData = new TbMsgMetaData();
 | 
			
		||||
        metaData.putValue("temp", "10");
 | 
			
		||||
        metaData.putValue("humidity", "99");
 | 
			
		||||
@ -89,15 +78,15 @@ public class TbJsSwitchNodeTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void allowedRelationPassed() throws TbNodeException {
 | 
			
		||||
        String jsCode = "function nextRelation(meta, msg) {\n" +
 | 
			
		||||
                "    if(msg.passed == 5 && meta.temp == 10)\n" +
 | 
			
		||||
        String jsCode = "function nextRelation(metadata, msg) {\n" +
 | 
			
		||||
                "    if(msg.passed == 5 && metadata.temp == 10)\n" +
 | 
			
		||||
                "        return 'one'\n" +
 | 
			
		||||
                "    else\n" +
 | 
			
		||||
                "        return 'two';\n" +
 | 
			
		||||
                "};\n" +
 | 
			
		||||
                "\n" +
 | 
			
		||||
                "nextRelation(meta, msg);";
 | 
			
		||||
        initWithScript(jsCode, Sets.newHashSet("one", "two"), false);
 | 
			
		||||
                "return nextRelation(metadata, msg);";
 | 
			
		||||
        initWithScript(jsCode);
 | 
			
		||||
        TbMsgMetaData metaData = new TbMsgMetaData();
 | 
			
		||||
        metaData.putValue("temp", "10");
 | 
			
		||||
        metaData.putValue("humidity", "99");
 | 
			
		||||
@ -111,32 +100,9 @@ public class TbJsSwitchNodeTest {
 | 
			
		||||
        verify(ctx).tellNext(msg, Sets.newHashSet("one"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void unknownRelationThrowsException() throws TbNodeException {
 | 
			
		||||
        String jsCode = "function nextRelation(meta, msg) {\n" +
 | 
			
		||||
                "    return ['one','nine'];" +
 | 
			
		||||
                "};\n" +
 | 
			
		||||
                "\n" +
 | 
			
		||||
                "nextRelation(meta, msg);";
 | 
			
		||||
        initWithScript(jsCode, Sets.newHashSet("one", "two"), false);
 | 
			
		||||
        TbMsgMetaData metaData = new TbMsgMetaData();
 | 
			
		||||
        metaData.putValue("temp", "10");
 | 
			
		||||
        metaData.putValue("humidity", "99");
 | 
			
		||||
        String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
 | 
			
		||||
 | 
			
		||||
        TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes());
 | 
			
		||||
        mockJsExecutor();
 | 
			
		||||
 | 
			
		||||
        node.onMsg(ctx, msg);
 | 
			
		||||
        verify(ctx).getJsExecutor();
 | 
			
		||||
        verifyError(msg, "Unsupported relation for switch [nine, one]", IllegalStateException.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initWithScript(String script, Set<String> relations, boolean routeToAll) throws TbNodeException {
 | 
			
		||||
    private void initWithScript(String script) throws TbNodeException {
 | 
			
		||||
        TbJsSwitchNodeConfiguration config = new TbJsSwitchNodeConfiguration();
 | 
			
		||||
        config.setJsScript(script);
 | 
			
		||||
        config.setAllowedRelations(relations);
 | 
			
		||||
        config.setRouteToAllWithNoCheck(routeToAll);
 | 
			
		||||
        ObjectMapper mapper = new ObjectMapper();
 | 
			
		||||
        TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -51,7 +51,7 @@ public class TbTransformMsgNodeTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void metadataCanBeUpdated() throws TbNodeException {
 | 
			
		||||
        initWithScript("meta.temp = meta.temp * 10;");
 | 
			
		||||
        initWithScript("return metadata.temp = metadata.temp * 10;");
 | 
			
		||||
        TbMsgMetaData metaData = new TbMsgMetaData();
 | 
			
		||||
        metaData.putValue("temp", "7");
 | 
			
		||||
        metaData.putValue("humidity", "99");
 | 
			
		||||
@ -70,7 +70,7 @@ public class TbTransformMsgNodeTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void metadataCanBeAdded() throws TbNodeException {
 | 
			
		||||
        initWithScript("meta.newAttr = meta.humidity - msg.passed;");
 | 
			
		||||
        initWithScript("return metadata.newAttr = metadata.humidity - msg.passed;");
 | 
			
		||||
        TbMsgMetaData metaData = new TbMsgMetaData();
 | 
			
		||||
        metaData.putValue("temp", "7");
 | 
			
		||||
        metaData.putValue("humidity", "99");
 | 
			
		||||
@ -89,7 +89,7 @@ public class TbTransformMsgNodeTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void payloadCanBeUpdated() throws TbNodeException {
 | 
			
		||||
        initWithScript("msg.passed = msg.passed * meta.temp; msg.bigObj.newProp = 'Ukraine' ");
 | 
			
		||||
        initWithScript("return msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine' ");
 | 
			
		||||
        TbMsgMetaData metaData = new TbMsgMetaData();
 | 
			
		||||
        metaData.putValue("temp", "7");
 | 
			
		||||
        metaData.putValue("humidity", "99");
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,6 @@ const forwardPort = 8080;
 | 
			
		||||
 | 
			
		||||
const ruleNodeUiforwardHost = 'localhost';
 | 
			
		||||
const ruleNodeUiforwardPort = 8080;
 | 
			
		||||
//const ruleNodeUiforwardPort = 5000;
 | 
			
		||||
 | 
			
		||||
const app = express();
 | 
			
		||||
const server = http.createServer(app);
 | 
			
		||||
 | 
			
		||||
@ -84,17 +84,32 @@ function JsonObjectEdit($compile, $templateCache, $document, toast, utils) {
 | 
			
		||||
        scope.$watch('contentBody', function (newVal, prevVal) {
 | 
			
		||||
            if (!angular.equals(newVal, prevVal)) {
 | 
			
		||||
                var object = scope.validate();
 | 
			
		||||
                ngModelCtrl.$setViewValue(object);
 | 
			
		||||
                if (scope.objectValid) {
 | 
			
		||||
                    if (object == null) {
 | 
			
		||||
                        scope.object = null;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (scope.object == null) {
 | 
			
		||||
                            scope.object = {};
 | 
			
		||||
                        }
 | 
			
		||||
                        Object.keys(scope.object).forEach(function (key) {
 | 
			
		||||
                            delete scope.object[key];
 | 
			
		||||
                        });
 | 
			
		||||
                        Object.keys(object).forEach(function (key) {
 | 
			
		||||
                            scope.object[key] = object[key];
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                    ngModelCtrl.$setViewValue(scope.object);
 | 
			
		||||
                }
 | 
			
		||||
                scope.updateValidity();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        ngModelCtrl.$render = function () {
 | 
			
		||||
            var object = ngModelCtrl.$viewValue;
 | 
			
		||||
            scope.object = ngModelCtrl.$viewValue;
 | 
			
		||||
            var content = '';
 | 
			
		||||
            try {
 | 
			
		||||
                if (object) {
 | 
			
		||||
                    content = angular.toJson(object, true);
 | 
			
		||||
                if (scope.object) {
 | 
			
		||||
                    content = angular.toJson(scope.object, true);
 | 
			
		||||
                }
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                //
 | 
			
		||||
 | 
			
		||||
@ -1171,6 +1171,7 @@ export default angular.module('thingsboard.locale', [])
 | 
			
		||||
                    "debug-mode": "Debug mode"
 | 
			
		||||
                },
 | 
			
		||||
                "rulenode": {
 | 
			
		||||
                    "details": "Details",
 | 
			
		||||
                    "add": "Add rule node",
 | 
			
		||||
                    "name": "Name",
 | 
			
		||||
                    "name-required": "Name is required.",
 | 
			
		||||
 | 
			
		||||
@ -256,6 +256,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
 | 
			
		||||
                vm.isEditingRuleNodeLink = true;
 | 
			
		||||
                vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
 | 
			
		||||
                vm.editingRuleNodeLink = angular.copy(edge);
 | 
			
		||||
                $mdUtil.nextTick(() => {
 | 
			
		||||
                    vm.ruleNodeLinkForm.$setPristine();
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        nodeCallbacks: {
 | 
			
		||||
@ -266,6 +269,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
 | 
			
		||||
                    vm.isEditingRuleNode = true;
 | 
			
		||||
                    vm.editingRuleNodeIndex = vm.ruleChainModel.nodes.indexOf(node);
 | 
			
		||||
                    vm.editingRuleNode = angular.copy(node);
 | 
			
		||||
                    $mdUtil.nextTick(() => {
 | 
			
		||||
                        vm.ruleNodeForm.$setPristine();
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@ -65,7 +65,8 @@
 | 
			
		||||
        </div>
 | 
			
		||||
        <tb-details-sidenav class="tb-rulenode-details-sidenav"
 | 
			
		||||
                            header-title="{{vm.editingRuleNode.name}}"
 | 
			
		||||
                            header-subtitle="{{'rulenode.rulenode-details' | translate}}"
 | 
			
		||||
                            header-subtitle="{{(vm.types.ruleNodeType[vm.editingRuleNode.component.type].name | translate)
 | 
			
		||||
                            + ' - ' + vm.editingRuleNode.component.name}}"
 | 
			
		||||
                            is-read-only="false"
 | 
			
		||||
                            is-open="vm.isEditingRuleNode"
 | 
			
		||||
                            is-always-edit="true"
 | 
			
		||||
@ -76,16 +77,20 @@
 | 
			
		||||
            <details-buttons tb-help="vm.helpLinkIdForRuleNodeType()" help-container-id="help-container">
 | 
			
		||||
                <div id="help-container"></div>
 | 
			
		||||
            </details-buttons>
 | 
			
		||||
            <form name="vm.ruleNodeForm" ng-if="vm.isEditingRuleNode">
 | 
			
		||||
                <tb-rule-node
 | 
			
		||||
                        rule-node="vm.editingRuleNode"
 | 
			
		||||
                        rule-chain-id="vm.ruleChain.id.id"
 | 
			
		||||
                        is-edit="true"
 | 
			
		||||
                        is-read-only="false"
 | 
			
		||||
                        on-delete-rule-node="vm.deleteRuleNode(event, vm.editingRuleNode)"
 | 
			
		||||
                        the-form="vm.ruleNodeForm">
 | 
			
		||||
                </tb-rule-node>
 | 
			
		||||
            </form>
 | 
			
		||||
            <md-tabs id="ruleNodeTabs" md-border-bottom flex class="tb-absolute-fill">
 | 
			
		||||
                <md-tab label="{{ 'rulenode.details' | translate }}">
 | 
			
		||||
                    <form name="vm.ruleNodeForm" ng-if="vm.isEditingRuleNode">
 | 
			
		||||
                        <tb-rule-node
 | 
			
		||||
                                rule-node="vm.editingRuleNode"
 | 
			
		||||
                                rule-chain-id="vm.ruleChain.id.id"
 | 
			
		||||
                                is-edit="true"
 | 
			
		||||
                                is-read-only="false"
 | 
			
		||||
                                on-delete-rule-node="vm.deleteRuleNode(event, vm.editingRuleNode)"
 | 
			
		||||
                                the-form="vm.ruleNodeForm">
 | 
			
		||||
                        </tb-rule-node>
 | 
			
		||||
                    </form>
 | 
			
		||||
                </md-tab>
 | 
			
		||||
            </md-tabs>
 | 
			
		||||
        </tb-details-sidenav>
 | 
			
		||||
        <tb-details-sidenav class="tb-rulenode-link-details-sidenav"
 | 
			
		||||
                            header-title="{{vm.editingRuleNodeLink.label}}"
 | 
			
		||||
 | 
			
		||||
@ -38,10 +38,15 @@ export default function RuleNodeConfigDirective($compile, $templateCache, $injec
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        scope.useDefinedDirective = function() {
 | 
			
		||||
            return scope.nodeDefinition.configDirective && !scope.definedDirectiveError;
 | 
			
		||||
            return scope.nodeDefinition &&
 | 
			
		||||
                scope.nodeDefinition.configDirective && !scope.definedDirectiveError;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        validateDefinedDirective();
 | 
			
		||||
        scope.$watch('nodeDefinition', () => {
 | 
			
		||||
            if (scope.nodeDefinition) {
 | 
			
		||||
                validateDefinedDirective();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        function validateDefinedDirective() {
 | 
			
		||||
            if (scope.nodeDefinition.uiResourceLoadError && scope.nodeDefinition.uiResourceLoadError.length) {
 | 
			
		||||
 | 
			
		||||
@ -36,10 +36,14 @@ export default function RuleNodeDefinedConfigDirective($compile) {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        function loadTemplate() {
 | 
			
		||||
            if (scope.ruleNodeConfigScope) {
 | 
			
		||||
                scope.ruleNodeConfigScope.$destroy();
 | 
			
		||||
            }
 | 
			
		||||
            var directive = snake_case(attrs.ruleNodeDirective, '-');
 | 
			
		||||
            var template = `<${directive} ng-model="configuration" ng-required="required" ng-readonly="readonly"></${directive}>`;
 | 
			
		||||
            element.html(template);
 | 
			
		||||
            $compile(element.contents())(scope);
 | 
			
		||||
            scope.ruleNodeConfigScope = scope.$new();
 | 
			
		||||
            $compile(element.contents())(scope.ruleNodeConfigScope);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function snake_case(name, separator) {
 | 
			
		||||
 | 
			
		||||
@ -21,33 +21,26 @@
 | 
			
		||||
 | 
			
		||||
<md-content class="md-padding tb-rulenode" layout="column">
 | 
			
		||||
    <fieldset ng-disabled="$root.loading || !isEdit || isReadOnly">
 | 
			
		||||
        <md-input-container class="md-block">
 | 
			
		||||
            <label translate>rulenode.type</label>
 | 
			
		||||
            <input readonly name="type" ng-model="ruleNode.component.name">
 | 
			
		||||
        </md-input-container>
 | 
			
		||||
        <section ng-if="ruleNode.component.type != types.ruleNodeType.RULE_CHAIN.value">
 | 
			
		||||
            <md-input-container class="md-block">
 | 
			
		||||
                <label translate>rulenode.name</label>
 | 
			
		||||
                <input required name="name" ng-model="ruleNode.name">
 | 
			
		||||
                <div ng-messages="theForm.name.$error">
 | 
			
		||||
                    <div translate ng-message="required">rulenode.name-required</div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </md-input-container>
 | 
			
		||||
            <md-input-container class="md-block">
 | 
			
		||||
                <md-checkbox ng-disabled="$root.loading || !isEdit" aria-label="{{ 'rulenode.debug-mode' | translate }}"
 | 
			
		||||
                             ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }}
 | 
			
		||||
                </md-checkbox>
 | 
			
		||||
            </md-input-container>
 | 
			
		||||
            <section layout="column" layout-gt-sm="row">
 | 
			
		||||
                <md-input-container flex class="md-block">
 | 
			
		||||
                    <label translate>rulenode.name</label>
 | 
			
		||||
                    <input required name="name" ng-model="ruleNode.name">
 | 
			
		||||
                    <div ng-messages="theForm.name.$error">
 | 
			
		||||
                        <div translate ng-message="required">rulenode.name-required</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </md-input-container>
 | 
			
		||||
                <md-input-container class="md-block">
 | 
			
		||||
                    <md-checkbox ng-disabled="$root.loading || !isEdit" aria-label="{{ 'rulenode.debug-mode' | translate }}"
 | 
			
		||||
                                 ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }}
 | 
			
		||||
                    </md-checkbox>
 | 
			
		||||
                </md-input-container>
 | 
			
		||||
            </section>
 | 
			
		||||
            <tb-rule-node-config ng-model="ruleNode.configuration"
 | 
			
		||||
                                 ng-required="true"
 | 
			
		||||
                                 node-definition="ruleNode.component.configurationDescriptor.nodeDefinition"
 | 
			
		||||
                                 ng-readonly="$root.loading || !isEdit || isReadOnly">
 | 
			
		||||
            </tb-rule-node-config>
 | 
			
		||||
            <!--tb-json-object-edit class="tb-rule-node-configuration-json" ng-model="ruleNode.configuration"
 | 
			
		||||
                                 label="{{ 'rulenode.configuration' | translate }}"
 | 
			
		||||
                                 ng-required="true"
 | 
			
		||||
                                 fill-height="true">
 | 
			
		||||
            </tb-json-object-edit-->
 | 
			
		||||
            <md-input-container class="md-block">
 | 
			
		||||
                <label translate>rulenode.description</label>
 | 
			
		||||
                <textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user