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