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. " +
|
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." +
|
"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 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"},
|
uiResources = {"static/rulenode/rulenode-core-config.js"},
|
||||||
configDirective = "tbFilterNodeScriptConfig")
|
configDirective = "tbFilterNodeScriptConfig")
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ public class TbJsFilterNode implements TbNode {
|
|||||||
@Override
|
@Override
|
||||||
public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
|
public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
|
||||||
this.config = TbNodeUtils.convert(configuration, TbJsFilterNodeConfiguration.class);
|
this.config = TbNodeUtils.convert(configuration, TbJsFilterNodeConfiguration.class);
|
||||||
this.jsEngine = new NashornJsEngine(config.getJsScript());
|
this.jsEngine = new NashornJsEngine(config.getJsScript(), "Filter");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -26,7 +26,7 @@ public class TbJsFilterNodeConfiguration implements NodeConfiguration {
|
|||||||
@Override
|
@Override
|
||||||
public TbJsFilterNodeConfiguration defaultConfiguration() {
|
public TbJsFilterNodeConfiguration defaultConfiguration() {
|
||||||
TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration();
|
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;
|
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. " +
|
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. " +
|
"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 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 {
|
public class TbJsSwitchNode implements TbNode {
|
||||||
|
|
||||||
private TbJsSwitchNodeConfiguration config;
|
private TbJsSwitchNodeConfiguration config;
|
||||||
@ -45,22 +47,11 @@ public class TbJsSwitchNode implements TbNode {
|
|||||||
@Override
|
@Override
|
||||||
public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
|
public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
|
||||||
this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class);
|
this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class);
|
||||||
if (config.getAllowedRelations().size() < 1) {
|
this.jsEngine = new NashornJsEngine(config.getJsScript(), "Switch");
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMsg(TbContext ctx, TbMsg msg) {
|
public void onMsg(TbContext ctx, TbMsg msg) {
|
||||||
if (config.isRouteToAllWithNoCheck()) {
|
|
||||||
ctx.tellNext(msg, config.getAllowedRelations());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ListeningExecutor jsExecutor = ctx.getJsExecutor();
|
ListeningExecutor jsExecutor = ctx.getJsExecutor();
|
||||||
withCallback(jsExecutor.executeAsync(() -> jsEngine.executeSwitch(toBindings(msg))),
|
withCallback(jsExecutor.executeAsync(() -> jsEngine.executeSwitch(toBindings(msg))),
|
||||||
result -> processSwitch(ctx, msg, result),
|
result -> processSwitch(ctx, msg, result),
|
||||||
@ -68,15 +59,7 @@ public class TbJsSwitchNode implements TbNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void processSwitch(TbContext ctx, TbMsg msg, Set<String> nextRelations) {
|
private void processSwitch(TbContext ctx, TbMsg msg, Set<String> nextRelations) {
|
||||||
if (validateRelations(nextRelations)) {
|
|
||||||
ctx.tellNext(msg, 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bindings toBindings(TbMsg msg) {
|
private Bindings toBindings(TbMsg msg) {
|
||||||
|
|||||||
@ -25,19 +25,15 @@ import java.util.Set;
|
|||||||
public class TbJsSwitchNodeConfiguration implements NodeConfiguration {
|
public class TbJsSwitchNodeConfiguration implements NodeConfiguration {
|
||||||
|
|
||||||
private String jsScript;
|
private String jsScript;
|
||||||
private Set<String> allowedRelations;
|
|
||||||
private boolean routeToAllWithNoCheck;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TbJsSwitchNodeConfiguration defaultConfiguration() {
|
public TbJsSwitchNodeConfiguration defaultConfiguration() {
|
||||||
TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration();
|
TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration();
|
||||||
configuration.setJsScript("function nextRelation(meta, msg) {\n" +
|
configuration.setJsScript("function nextRelation(metadata, msg) {\n" +
|
||||||
" return ['one','nine'];" +
|
" return ['one','nine'];" +
|
||||||
"};\n" +
|
"};\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"nextRelation(meta, msg);");
|
"return nextRelation(metadata, msg);");
|
||||||
configuration.setAllowedRelations(Sets.newHashSet("one", "two"));
|
|
||||||
configuration.setRouteToAllWithNoCheck(false);
|
|
||||||
return configuration;
|
return configuration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,9 @@ import org.thingsboard.server.common.msg.TbMsg;
|
|||||||
configClazz = TbMsgTypeFilterNodeConfiguration.class,
|
configClazz = TbMsgTypeFilterNodeConfiguration.class,
|
||||||
nodeDescription = "Filter incoming messages by Message Type",
|
nodeDescription = "Filter incoming messages by Message Type",
|
||||||
nodeDetails = "Evaluate incoming Message with configured JS condition. " +
|
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 {
|
public class TbMsgTypeFilterNode implements TbNode {
|
||||||
|
|
||||||
TbMsgTypeFilterNodeConfiguration config;
|
TbMsgTypeFilterNodeConfiguration config;
|
||||||
|
|||||||
@ -33,7 +33,7 @@ public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration {
|
|||||||
@Override
|
@Override
|
||||||
public TbMsgTypeFilterNodeConfiguration defaultConfiguration() {
|
public TbMsgTypeFilterNodeConfiguration defaultConfiguration() {
|
||||||
TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration();
|
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;
|
return configuration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,14 +34,20 @@ import java.util.Set;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class NashornJsEngine {
|
public class NashornJsEngine {
|
||||||
|
|
||||||
public static final String METADATA = "meta";
|
public static final String METADATA = "metadata";
|
||||||
public static final String DATA = "msg";
|
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 static NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
|
||||||
|
|
||||||
private CompiledScript engine;
|
private CompiledScript engine;
|
||||||
|
|
||||||
public NashornJsEngine(String script) {
|
public NashornJsEngine(String script, String functionName) {
|
||||||
engine = compileScript(script);
|
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) {
|
private static CompiledScript compileScript(String script) {
|
||||||
@ -58,15 +64,15 @@ public class NashornJsEngine {
|
|||||||
public static Bindings bindMsg(TbMsg msg) {
|
public static Bindings bindMsg(TbMsg msg) {
|
||||||
try {
|
try {
|
||||||
Bindings bindings = new SimpleBindings();
|
Bindings bindings = new SimpleBindings();
|
||||||
bindings.put(METADATA, msg.getMetaData().getData());
|
|
||||||
|
|
||||||
if (ArrayUtils.isNotEmpty(msg.getData())) {
|
if (ArrayUtils.isNotEmpty(msg.getData())) {
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
JsonNode jsonNode = mapper.readTree(msg.getData());
|
JsonNode jsonNode = mapper.readTree(msg.getData());
|
||||||
Map map = mapper.treeToValue(jsonNode, Map.class);
|
Map map = mapper.treeToValue(jsonNode, Map.class);
|
||||||
bindings.put(DATA, map);
|
bindings.put(DATA, map);
|
||||||
|
} else {
|
||||||
|
bindings.put(DATA, Collections.emptyMap());
|
||||||
}
|
}
|
||||||
|
bindings.put(METADATA, msg.getMetaData().getData());
|
||||||
return bindings;
|
return bindings;
|
||||||
} catch (Throwable th) {
|
} catch (Throwable th) {
|
||||||
throw new IllegalArgumentException("Cannot bind js args", 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",
|
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 " +
|
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 " +
|
"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.")
|
"If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.")
|
||||||
public class TbGetAttributesNode implements TbNode {
|
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",
|
nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata",
|
||||||
nodeDetails = "If Attributes enrichment configured, server scope attributes are added 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 " +
|
"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> {
|
public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> {
|
||||||
|
|
||||||
@Override
|
@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 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. " +
|
"If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
|
||||||
"To access those attributes in other nodes this template can be used " +
|
"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> {
|
public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> {
|
||||||
|
|
||||||
private TbGetRelatedAttrNodeConfiguration config;
|
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",
|
nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata",
|
||||||
nodeDetails = "If Attributes enrichment configured, server scope attributes are added 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 " +
|
"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> {
|
public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import javax.script.Bindings;
|
|||||||
configClazz = TbTransformMsgNodeConfiguration.class,
|
configClazz = TbTransformMsgNodeConfiguration.class,
|
||||||
nodeDescription = "Change Message payload and Metadata using JavaScript",
|
nodeDescription = "Change Message payload and Metadata using JavaScript",
|
||||||
nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside.<br/> " +
|
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.")
|
"<code>msg</code> - is a Message payload.<br/>Any properties can be changed/removed/added in those objects.")
|
||||||
public class TbTransformMsgNode extends TbAbstractTransformNode {
|
public class TbTransformMsgNode extends TbAbstractTransformNode {
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ public class TbTransformMsgNode extends TbAbstractTransformNode {
|
|||||||
@Override
|
@Override
|
||||||
public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
|
public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
|
||||||
this.config = TbNodeUtils.convert(configuration, TbTransformMsgNodeConfiguration.class);
|
this.config = TbNodeUtils.convert(configuration, TbTransformMsgNodeConfiguration.class);
|
||||||
this.jsEngine = new NashornJsEngine(config.getJsScript());
|
this.jsEngine = new NashornJsEngine(config.getJsScript(), "Transform");
|
||||||
setConfig(config);
|
setConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@ public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguratio
|
|||||||
public TbTransformMsgNodeConfiguration defaultConfiguration() {
|
public TbTransformMsgNodeConfiguration defaultConfiguration() {
|
||||||
TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration();
|
TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration();
|
||||||
configuration.setStartNewChain(false);
|
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;
|
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
|
@Test
|
||||||
public void falseEvaluationDoNotSendMsg() throws TbNodeException {
|
public void falseEvaluationDoNotSendMsg() throws TbNodeException {
|
||||||
initWithScript("10 > 15;");
|
initWithScript("return 10 > 15;");
|
||||||
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes());
|
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes());
|
||||||
|
|
||||||
mockJsExecutor();
|
mockJsExecutor();
|
||||||
@ -64,7 +64,7 @@ public class TbJsFilterNodeTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void notValidMsgDataThrowsException() throws TbNodeException {
|
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]);
|
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), new byte[4]);
|
||||||
|
|
||||||
when(ctx.getJsExecutor()).thenReturn(executor);
|
when(ctx.getJsExecutor()).thenReturn(executor);
|
||||||
@ -77,7 +77,7 @@ public class TbJsFilterNodeTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void exceptionInJsThrowsException() throws TbNodeException {
|
public void exceptionInJsThrowsException() throws TbNodeException {
|
||||||
initWithScript("meta.temp.curr < 15;");
|
initWithScript("return metadata.temp.curr < 15;");
|
||||||
TbMsgMetaData metaData = new TbMsgMetaData();
|
TbMsgMetaData metaData = new TbMsgMetaData();
|
||||||
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes());
|
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes());
|
||||||
mockJsExecutor();
|
mockJsExecutor();
|
||||||
@ -89,12 +89,12 @@ public class TbJsFilterNodeTest {
|
|||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void notValidScriptThrowsException() throws TbNodeException {
|
public void notValidScriptThrowsException() throws TbNodeException {
|
||||||
initWithScript("10 > 15 asdq out");
|
initWithScript("return 10 > 15 asdq out");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void metadataConditionCanBeFalse() throws TbNodeException {
|
public void metadataConditionCanBeFalse() throws TbNodeException {
|
||||||
initWithScript("meta.humidity < 15;");
|
initWithScript("return metadata.humidity < 15;");
|
||||||
TbMsgMetaData metaData = new TbMsgMetaData();
|
TbMsgMetaData metaData = new TbMsgMetaData();
|
||||||
metaData.putValue("temp", "10");
|
metaData.putValue("temp", "10");
|
||||||
metaData.putValue("humidity", "99");
|
metaData.putValue("humidity", "99");
|
||||||
@ -109,7 +109,7 @@ public class TbJsFilterNodeTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void metadataConditionCanBeTrue() throws TbNodeException {
|
public void metadataConditionCanBeTrue() throws TbNodeException {
|
||||||
initWithScript("meta.temp < 15;");
|
initWithScript("return metadata.temp < 15;");
|
||||||
TbMsgMetaData metaData = new TbMsgMetaData();
|
TbMsgMetaData metaData = new TbMsgMetaData();
|
||||||
metaData.putValue("temp", "10");
|
metaData.putValue("temp", "10");
|
||||||
metaData.putValue("humidity", "99");
|
metaData.putValue("humidity", "99");
|
||||||
@ -123,7 +123,7 @@ public class TbJsFilterNodeTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void msgJsonParsedAndBinded() throws TbNodeException {
|
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();
|
TbMsgMetaData metaData = new TbMsgMetaData();
|
||||||
metaData.putValue("temp", "10");
|
metaData.putValue("temp", "10");
|
||||||
metaData.putValue("humidity", "99");
|
metaData.putValue("humidity", "99");
|
||||||
|
|||||||
@ -52,28 +52,17 @@ public class TbJsSwitchNodeTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private ListeningExecutor executor;
|
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
|
@Test
|
||||||
public void multipleRoutesAreAllowed() throws TbNodeException {
|
public void multipleRoutesAreAllowed() throws TbNodeException {
|
||||||
String jsCode = "function nextRelation(meta, msg) {\n" +
|
String jsCode = "function nextRelation(metadata, msg) {\n" +
|
||||||
" if(msg.passed == 5 && meta.temp == 10)\n" +
|
" if(msg.passed == 5 && metadata.temp == 10)\n" +
|
||||||
" return ['three', 'one']\n" +
|
" return ['three', 'one']\n" +
|
||||||
" else\n" +
|
" else\n" +
|
||||||
" return 'two';\n" +
|
" return 'two';\n" +
|
||||||
"};\n" +
|
"};\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"nextRelation(meta, msg);";
|
"return nextRelation(metadata, msg);";
|
||||||
initWithScript(jsCode, Sets.newHashSet("one", "two", "three"), false);
|
initWithScript(jsCode);
|
||||||
TbMsgMetaData metaData = new TbMsgMetaData();
|
TbMsgMetaData metaData = new TbMsgMetaData();
|
||||||
metaData.putValue("temp", "10");
|
metaData.putValue("temp", "10");
|
||||||
metaData.putValue("humidity", "99");
|
metaData.putValue("humidity", "99");
|
||||||
@ -89,15 +78,15 @@ public class TbJsSwitchNodeTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void allowedRelationPassed() throws TbNodeException {
|
public void allowedRelationPassed() throws TbNodeException {
|
||||||
String jsCode = "function nextRelation(meta, msg) {\n" +
|
String jsCode = "function nextRelation(metadata, msg) {\n" +
|
||||||
" if(msg.passed == 5 && meta.temp == 10)\n" +
|
" if(msg.passed == 5 && metadata.temp == 10)\n" +
|
||||||
" return 'one'\n" +
|
" return 'one'\n" +
|
||||||
" else\n" +
|
" else\n" +
|
||||||
" return 'two';\n" +
|
" return 'two';\n" +
|
||||||
"};\n" +
|
"};\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"nextRelation(meta, msg);";
|
"return nextRelation(metadata, msg);";
|
||||||
initWithScript(jsCode, Sets.newHashSet("one", "two"), false);
|
initWithScript(jsCode);
|
||||||
TbMsgMetaData metaData = new TbMsgMetaData();
|
TbMsgMetaData metaData = new TbMsgMetaData();
|
||||||
metaData.putValue("temp", "10");
|
metaData.putValue("temp", "10");
|
||||||
metaData.putValue("humidity", "99");
|
metaData.putValue("humidity", "99");
|
||||||
@ -111,32 +100,9 @@ public class TbJsSwitchNodeTest {
|
|||||||
verify(ctx).tellNext(msg, Sets.newHashSet("one"));
|
verify(ctx).tellNext(msg, Sets.newHashSet("one"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
private void initWithScript(String script) throws TbNodeException {
|
||||||
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 {
|
|
||||||
TbJsSwitchNodeConfiguration config = new TbJsSwitchNodeConfiguration();
|
TbJsSwitchNodeConfiguration config = new TbJsSwitchNodeConfiguration();
|
||||||
config.setJsScript(script);
|
config.setJsScript(script);
|
||||||
config.setAllowedRelations(relations);
|
|
||||||
config.setRouteToAllWithNoCheck(routeToAll);
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
|
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
|
||||||
|
|
||||||
|
|||||||
@ -51,7 +51,7 @@ public class TbTransformMsgNodeTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void metadataCanBeUpdated() throws TbNodeException {
|
public void metadataCanBeUpdated() throws TbNodeException {
|
||||||
initWithScript("meta.temp = meta.temp * 10;");
|
initWithScript("return metadata.temp = metadata.temp * 10;");
|
||||||
TbMsgMetaData metaData = new TbMsgMetaData();
|
TbMsgMetaData metaData = new TbMsgMetaData();
|
||||||
metaData.putValue("temp", "7");
|
metaData.putValue("temp", "7");
|
||||||
metaData.putValue("humidity", "99");
|
metaData.putValue("humidity", "99");
|
||||||
@ -70,7 +70,7 @@ public class TbTransformMsgNodeTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void metadataCanBeAdded() throws TbNodeException {
|
public void metadataCanBeAdded() throws TbNodeException {
|
||||||
initWithScript("meta.newAttr = meta.humidity - msg.passed;");
|
initWithScript("return metadata.newAttr = metadata.humidity - msg.passed;");
|
||||||
TbMsgMetaData metaData = new TbMsgMetaData();
|
TbMsgMetaData metaData = new TbMsgMetaData();
|
||||||
metaData.putValue("temp", "7");
|
metaData.putValue("temp", "7");
|
||||||
metaData.putValue("humidity", "99");
|
metaData.putValue("humidity", "99");
|
||||||
@ -89,7 +89,7 @@ public class TbTransformMsgNodeTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void payloadCanBeUpdated() throws TbNodeException {
|
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();
|
TbMsgMetaData metaData = new TbMsgMetaData();
|
||||||
metaData.putValue("temp", "7");
|
metaData.putValue("temp", "7");
|
||||||
metaData.putValue("humidity", "99");
|
metaData.putValue("humidity", "99");
|
||||||
|
|||||||
@ -32,7 +32,6 @@ const forwardPort = 8080;
|
|||||||
|
|
||||||
const ruleNodeUiforwardHost = 'localhost';
|
const ruleNodeUiforwardHost = 'localhost';
|
||||||
const ruleNodeUiforwardPort = 8080;
|
const ruleNodeUiforwardPort = 8080;
|
||||||
//const ruleNodeUiforwardPort = 5000;
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
|
|||||||
@ -84,17 +84,32 @@ function JsonObjectEdit($compile, $templateCache, $document, toast, utils) {
|
|||||||
scope.$watch('contentBody', function (newVal, prevVal) {
|
scope.$watch('contentBody', function (newVal, prevVal) {
|
||||||
if (!angular.equals(newVal, prevVal)) {
|
if (!angular.equals(newVal, prevVal)) {
|
||||||
var object = scope.validate();
|
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();
|
scope.updateValidity();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ngModelCtrl.$render = function () {
|
ngModelCtrl.$render = function () {
|
||||||
var object = ngModelCtrl.$viewValue;
|
scope.object = ngModelCtrl.$viewValue;
|
||||||
var content = '';
|
var content = '';
|
||||||
try {
|
try {
|
||||||
if (object) {
|
if (scope.object) {
|
||||||
content = angular.toJson(object, true);
|
content = angular.toJson(scope.object, true);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1171,6 +1171,7 @@ export default angular.module('thingsboard.locale', [])
|
|||||||
"debug-mode": "Debug mode"
|
"debug-mode": "Debug mode"
|
||||||
},
|
},
|
||||||
"rulenode": {
|
"rulenode": {
|
||||||
|
"details": "Details",
|
||||||
"add": "Add rule node",
|
"add": "Add rule node",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"name-required": "Name is required.",
|
"name-required": "Name is required.",
|
||||||
|
|||||||
@ -256,6 +256,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
|
|||||||
vm.isEditingRuleNodeLink = true;
|
vm.isEditingRuleNodeLink = true;
|
||||||
vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
|
vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
|
||||||
vm.editingRuleNodeLink = angular.copy(edge);
|
vm.editingRuleNodeLink = angular.copy(edge);
|
||||||
|
$mdUtil.nextTick(() => {
|
||||||
|
vm.ruleNodeLinkForm.$setPristine();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
nodeCallbacks: {
|
nodeCallbacks: {
|
||||||
@ -266,6 +269,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
|
|||||||
vm.isEditingRuleNode = true;
|
vm.isEditingRuleNode = true;
|
||||||
vm.editingRuleNodeIndex = vm.ruleChainModel.nodes.indexOf(node);
|
vm.editingRuleNodeIndex = vm.ruleChainModel.nodes.indexOf(node);
|
||||||
vm.editingRuleNode = angular.copy(node);
|
vm.editingRuleNode = angular.copy(node);
|
||||||
|
$mdUtil.nextTick(() => {
|
||||||
|
vm.ruleNodeForm.$setPristine();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -65,7 +65,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<tb-details-sidenav class="tb-rulenode-details-sidenav"
|
<tb-details-sidenav class="tb-rulenode-details-sidenav"
|
||||||
header-title="{{vm.editingRuleNode.name}}"
|
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-read-only="false"
|
||||||
is-open="vm.isEditingRuleNode"
|
is-open="vm.isEditingRuleNode"
|
||||||
is-always-edit="true"
|
is-always-edit="true"
|
||||||
@ -76,6 +77,8 @@
|
|||||||
<details-buttons tb-help="vm.helpLinkIdForRuleNodeType()" help-container-id="help-container">
|
<details-buttons tb-help="vm.helpLinkIdForRuleNodeType()" help-container-id="help-container">
|
||||||
<div id="help-container"></div>
|
<div id="help-container"></div>
|
||||||
</details-buttons>
|
</details-buttons>
|
||||||
|
<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">
|
<form name="vm.ruleNodeForm" ng-if="vm.isEditingRuleNode">
|
||||||
<tb-rule-node
|
<tb-rule-node
|
||||||
rule-node="vm.editingRuleNode"
|
rule-node="vm.editingRuleNode"
|
||||||
@ -86,6 +89,8 @@
|
|||||||
the-form="vm.ruleNodeForm">
|
the-form="vm.ruleNodeForm">
|
||||||
</tb-rule-node>
|
</tb-rule-node>
|
||||||
</form>
|
</form>
|
||||||
|
</md-tab>
|
||||||
|
</md-tabs>
|
||||||
</tb-details-sidenav>
|
</tb-details-sidenav>
|
||||||
<tb-details-sidenav class="tb-rulenode-link-details-sidenav"
|
<tb-details-sidenav class="tb-rulenode-link-details-sidenav"
|
||||||
header-title="{{vm.editingRuleNodeLink.label}}"
|
header-title="{{vm.editingRuleNodeLink.label}}"
|
||||||
|
|||||||
@ -38,10 +38,15 @@ export default function RuleNodeConfigDirective($compile, $templateCache, $injec
|
|||||||
};
|
};
|
||||||
|
|
||||||
scope.useDefinedDirective = function() {
|
scope.useDefinedDirective = function() {
|
||||||
return scope.nodeDefinition.configDirective && !scope.definedDirectiveError;
|
return scope.nodeDefinition &&
|
||||||
|
scope.nodeDefinition.configDirective && !scope.definedDirectiveError;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
scope.$watch('nodeDefinition', () => {
|
||||||
|
if (scope.nodeDefinition) {
|
||||||
validateDefinedDirective();
|
validateDefinedDirective();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function validateDefinedDirective() {
|
function validateDefinedDirective() {
|
||||||
if (scope.nodeDefinition.uiResourceLoadError && scope.nodeDefinition.uiResourceLoadError.length) {
|
if (scope.nodeDefinition.uiResourceLoadError && scope.nodeDefinition.uiResourceLoadError.length) {
|
||||||
|
|||||||
@ -36,10 +36,14 @@ export default function RuleNodeDefinedConfigDirective($compile) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function loadTemplate() {
|
function loadTemplate() {
|
||||||
|
if (scope.ruleNodeConfigScope) {
|
||||||
|
scope.ruleNodeConfigScope.$destroy();
|
||||||
|
}
|
||||||
var directive = snake_case(attrs.ruleNodeDirective, '-');
|
var directive = snake_case(attrs.ruleNodeDirective, '-');
|
||||||
var template = `<${directive} ng-model="configuration" ng-required="required" ng-readonly="readonly"></${directive}>`;
|
var template = `<${directive} ng-model="configuration" ng-required="required" ng-readonly="readonly"></${directive}>`;
|
||||||
element.html(template);
|
element.html(template);
|
||||||
$compile(element.contents())(scope);
|
scope.ruleNodeConfigScope = scope.$new();
|
||||||
|
$compile(element.contents())(scope.ruleNodeConfigScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
function snake_case(name, separator) {
|
function snake_case(name, separator) {
|
||||||
|
|||||||
@ -21,12 +21,9 @@
|
|||||||
|
|
||||||
<md-content class="md-padding tb-rulenode" layout="column">
|
<md-content class="md-padding tb-rulenode" layout="column">
|
||||||
<fieldset ng-disabled="$root.loading || !isEdit || isReadOnly">
|
<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">
|
<section ng-if="ruleNode.component.type != types.ruleNodeType.RULE_CHAIN.value">
|
||||||
<md-input-container class="md-block">
|
<section layout="column" layout-gt-sm="row">
|
||||||
|
<md-input-container flex class="md-block">
|
||||||
<label translate>rulenode.name</label>
|
<label translate>rulenode.name</label>
|
||||||
<input required name="name" ng-model="ruleNode.name">
|
<input required name="name" ng-model="ruleNode.name">
|
||||||
<div ng-messages="theForm.name.$error">
|
<div ng-messages="theForm.name.$error">
|
||||||
@ -38,16 +35,12 @@
|
|||||||
ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }}
|
ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }}
|
||||||
</md-checkbox>
|
</md-checkbox>
|
||||||
</md-input-container>
|
</md-input-container>
|
||||||
|
</section>
|
||||||
<tb-rule-node-config ng-model="ruleNode.configuration"
|
<tb-rule-node-config ng-model="ruleNode.configuration"
|
||||||
ng-required="true"
|
ng-required="true"
|
||||||
node-definition="ruleNode.component.configurationDescriptor.nodeDefinition"
|
node-definition="ruleNode.component.configurationDescriptor.nodeDefinition"
|
||||||
ng-readonly="$root.loading || !isEdit || isReadOnly">
|
ng-readonly="$root.loading || !isEdit || isReadOnly">
|
||||||
</tb-rule-node-config>
|
</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">
|
<md-input-container class="md-block">
|
||||||
<label translate>rulenode.description</label>
|
<label translate>rulenode.description</label>
|
||||||
<textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea>
|
<textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user