Commit 784de0836fca6c6ea95de8061dd4532623639332

Authored by Igor Kulikov
1 parent fd1199ee

Filter nodes UI configuration.

Showing 26 changed files with 123 additions and 140 deletions
@@ -35,7 +35,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -35,7 +35,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
35 nodeDetails = "Evaluate incoming Message with configured JS condition. " + 35 nodeDetails = "Evaluate incoming Message with configured JS condition. " +
36 "If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." + 36 "If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." +
37 "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code>" + 37 "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code>" +
38 - "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>", 38 + "Message metadata can be accessed via <code>metadata</code> property. For example <code>metadata.customerName === 'John';</code>",
39 uiResources = {"static/rulenode/rulenode-core-config.js"}, 39 uiResources = {"static/rulenode/rulenode-core-config.js"},
40 configDirective = "tbFilterNodeScriptConfig") 40 configDirective = "tbFilterNodeScriptConfig")
41 41
@@ -47,7 +47,7 @@ public class TbJsFilterNode implements TbNode { @@ -47,7 +47,7 @@ public class TbJsFilterNode implements TbNode {
47 @Override 47 @Override
48 public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { 48 public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
49 this.config = TbNodeUtils.convert(configuration, TbJsFilterNodeConfiguration.class); 49 this.config = TbNodeUtils.convert(configuration, TbJsFilterNodeConfiguration.class);
50 - this.jsEngine = new NashornJsEngine(config.getJsScript()); 50 + this.jsEngine = new NashornJsEngine(config.getJsScript(), "Filter");
51 } 51 }
52 52
53 @Override 53 @Override
@@ -26,7 +26,7 @@ public class TbJsFilterNodeConfiguration implements NodeConfiguration { @@ -26,7 +26,7 @@ public class TbJsFilterNodeConfiguration implements NodeConfiguration {
26 @Override 26 @Override
27 public TbJsFilterNodeConfiguration defaultConfiguration() { 27 public TbJsFilterNodeConfiguration defaultConfiguration() {
28 TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration(); 28 TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration();
29 - configuration.setJsScript("msg.passed < 15 && msg.name === 'Vit' && meta.temp == 10 && msg.bigObj.prop == 42;"); 29 + configuration.setJsScript("return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 10 && msg.bigObj.prop == 42;");
30 return configuration; 30 return configuration;
31 } 31 }
32 } 32 }
@@ -36,7 +36,9 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -36,7 +36,9 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
36 nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " + 36 nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " +
37 "If Array is empty - message not routed to next Node. " + 37 "If Array is empty - message not routed to next Node. " +
38 "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code> " + 38 "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code> " +
39 - "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>") 39 + "Message metadata can be accessed via <code>metadata</code> property. For example <code>metadata.customerName === 'John';</code>",
  40 + uiResources = {"static/rulenode/rulenode-core-config.js"},
  41 + configDirective = "tbFilterNodeSwitchConfig")
40 public class TbJsSwitchNode implements TbNode { 42 public class TbJsSwitchNode implements TbNode {
41 43
42 private TbJsSwitchNodeConfiguration config; 44 private TbJsSwitchNodeConfiguration config;
@@ -45,22 +47,11 @@ public class TbJsSwitchNode implements TbNode { @@ -45,22 +47,11 @@ public class TbJsSwitchNode implements TbNode {
45 @Override 47 @Override
46 public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { 48 public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
47 this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class); 49 this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class);
48 - if (config.getAllowedRelations().size() < 1) {  
49 - String message = "Switch node should have at least 1 relation";  
50 - log.error(message);  
51 - throw new IllegalStateException(message);  
52 - }  
53 - if (!config.isRouteToAllWithNoCheck()) {  
54 - this.jsEngine = new NashornJsEngine(config.getJsScript());  
55 - } 50 + this.jsEngine = new NashornJsEngine(config.getJsScript(), "Switch");
56 } 51 }
57 52
58 @Override 53 @Override
59 public void onMsg(TbContext ctx, TbMsg msg) { 54 public void onMsg(TbContext ctx, TbMsg msg) {
60 - if (config.isRouteToAllWithNoCheck()) {  
61 - ctx.tellNext(msg, config.getAllowedRelations());  
62 - return;  
63 - }  
64 ListeningExecutor jsExecutor = ctx.getJsExecutor(); 55 ListeningExecutor jsExecutor = ctx.getJsExecutor();
65 withCallback(jsExecutor.executeAsync(() -> jsEngine.executeSwitch(toBindings(msg))), 56 withCallback(jsExecutor.executeAsync(() -> jsEngine.executeSwitch(toBindings(msg))),
66 result -> processSwitch(ctx, msg, result), 57 result -> processSwitch(ctx, msg, result),
@@ -68,15 +59,7 @@ public class TbJsSwitchNode implements TbNode { @@ -68,15 +59,7 @@ public class TbJsSwitchNode implements TbNode {
68 } 59 }
69 60
70 private void processSwitch(TbContext ctx, TbMsg msg, Set<String> nextRelations) { 61 private void processSwitch(TbContext ctx, TbMsg msg, Set<String> nextRelations) {
71 - if (validateRelations(nextRelations)) {  
72 - ctx.tellNext(msg, nextRelations);  
73 - } else {  
74 - ctx.tellError(msg, new IllegalStateException("Unsupported relation for switch " + nextRelations));  
75 - }  
76 - }  
77 -  
78 - private boolean validateRelations(Set<String> nextRelations) {  
79 - return config.getAllowedRelations().containsAll(nextRelations); 62 + ctx.tellNext(msg, nextRelations);
80 } 63 }
81 64
82 private Bindings toBindings(TbMsg msg) { 65 private Bindings toBindings(TbMsg msg) {
@@ -25,19 +25,15 @@ import java.util.Set; @@ -25,19 +25,15 @@ import java.util.Set;
25 public class TbJsSwitchNodeConfiguration implements NodeConfiguration { 25 public class TbJsSwitchNodeConfiguration implements NodeConfiguration {
26 26
27 private String jsScript; 27 private String jsScript;
28 - private Set<String> allowedRelations;  
29 - private boolean routeToAllWithNoCheck;  
30 28
31 @Override 29 @Override
32 public TbJsSwitchNodeConfiguration defaultConfiguration() { 30 public TbJsSwitchNodeConfiguration defaultConfiguration() {
33 TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration(); 31 TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration();
34 - configuration.setJsScript("function nextRelation(meta, msg) {\n" + 32 + configuration.setJsScript("function nextRelation(metadata, msg) {\n" +
35 " return ['one','nine'];" + 33 " return ['one','nine'];" +
36 "};\n" + 34 "};\n" +
37 "\n" + 35 "\n" +
38 - "nextRelation(meta, msg);");  
39 - configuration.setAllowedRelations(Sets.newHashSet("one", "two"));  
40 - configuration.setRouteToAllWithNoCheck(false); 36 + "return nextRelation(metadata, msg);");
41 return configuration; 37 return configuration;
42 } 38 }
43 } 39 }
@@ -31,7 +31,9 @@ import org.thingsboard.server.common.msg.TbMsg; @@ -31,7 +31,9 @@ import org.thingsboard.server.common.msg.TbMsg;
31 configClazz = TbMsgTypeFilterNodeConfiguration.class, 31 configClazz = TbMsgTypeFilterNodeConfiguration.class,
32 nodeDescription = "Filter incoming messages by Message Type", 32 nodeDescription = "Filter incoming messages by Message Type",
33 nodeDetails = "Evaluate incoming Message with configured JS condition. " + 33 nodeDetails = "Evaluate incoming Message with configured JS condition. " +
34 - "If incoming MessageType is expected - send Message via <b>Success</b> chain, otherwise <b>Failure</b> chain is used.") 34 + "If incoming MessageType is expected - send Message via <b>Success</b> chain, otherwise <b>Failure</b> chain is used.",
  35 + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
  36 + configDirective = "tbFilterNodeMessageTypeConfig")
35 public class TbMsgTypeFilterNode implements TbNode { 37 public class TbMsgTypeFilterNode implements TbNode {
36 38
37 TbMsgTypeFilterNodeConfiguration config; 39 TbMsgTypeFilterNodeConfiguration config;
@@ -33,7 +33,7 @@ public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration { @@ -33,7 +33,7 @@ public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration {
33 @Override 33 @Override
34 public TbMsgTypeFilterNodeConfiguration defaultConfiguration() { 34 public TbMsgTypeFilterNodeConfiguration defaultConfiguration() {
35 TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration(); 35 TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration();
36 - configuration.setMessageTypes(Arrays.asList("GET_ATTRIBUTES","POST_ATTRIBUTES","POST_TELEMETRY","RPC_REQUEST")); 36 + configuration.setMessageTypes(Arrays.asList("POST_ATTRIBUTES","POST_TELEMETRY","RPC_REQUEST"));
37 return configuration; 37 return configuration;
38 } 38 }
39 } 39 }
@@ -34,14 +34,20 @@ import java.util.Set; @@ -34,14 +34,20 @@ import java.util.Set;
34 @Slf4j 34 @Slf4j
35 public class NashornJsEngine { 35 public class NashornJsEngine {
36 36
37 - public static final String METADATA = "meta"; 37 + public static final String METADATA = "metadata";
38 public static final String DATA = "msg"; 38 public static final String DATA = "msg";
  39 +
  40 + private static final String JS_WRAPPER_PREFIX_TEMPLATE = "function %s(msg, metadata) { ";
  41 + private static final String JS_WRAPPER_SUFFIX_TEMPLATE = "}\n %s(msg, metadata);";
  42 +
39 private static NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); 43 private static NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
40 44
41 private CompiledScript engine; 45 private CompiledScript engine;
42 46
43 - public NashornJsEngine(String script) {  
44 - engine = compileScript(script); 47 + public NashornJsEngine(String script, String functionName) {
  48 + String jsWrapperPrefix = String.format(JS_WRAPPER_PREFIX_TEMPLATE, functionName);
  49 + String jsWrapperSuffix = String.format(JS_WRAPPER_SUFFIX_TEMPLATE, functionName);
  50 + engine = compileScript(jsWrapperPrefix + script + jsWrapperSuffix);
45 } 51 }
46 52
47 private static CompiledScript compileScript(String script) { 53 private static CompiledScript compileScript(String script) {
@@ -58,15 +64,15 @@ public class NashornJsEngine { @@ -58,15 +64,15 @@ public class NashornJsEngine {
58 public static Bindings bindMsg(TbMsg msg) { 64 public static Bindings bindMsg(TbMsg msg) {
59 try { 65 try {
60 Bindings bindings = new SimpleBindings(); 66 Bindings bindings = new SimpleBindings();
61 - bindings.put(METADATA, msg.getMetaData().getData());  
62 -  
63 if (ArrayUtils.isNotEmpty(msg.getData())) { 67 if (ArrayUtils.isNotEmpty(msg.getData())) {
64 ObjectMapper mapper = new ObjectMapper(); 68 ObjectMapper mapper = new ObjectMapper();
65 JsonNode jsonNode = mapper.readTree(msg.getData()); 69 JsonNode jsonNode = mapper.readTree(msg.getData());
66 Map map = mapper.treeToValue(jsonNode, Map.class); 70 Map map = mapper.treeToValue(jsonNode, Map.class);
67 bindings.put(DATA, map); 71 bindings.put(DATA, map);
  72 + } else {
  73 + bindings.put(DATA, Collections.emptyMap());
68 } 74 }
69 - 75 + bindings.put(METADATA, msg.getMetaData().getData());
70 return bindings; 76 return bindings;
71 } catch (Throwable th) { 77 } catch (Throwable th) {
72 throw new IllegalArgumentException("Cannot bind js args", th); 78 throw new IllegalArgumentException("Cannot bind js args", th);
@@ -42,7 +42,7 @@ import static org.thingsboard.server.common.data.DataConstants.*; @@ -42,7 +42,7 @@ import static org.thingsboard.server.common.data.DataConstants.*;
42 nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata", 42 nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata",
43 nodeDetails = "If Attributes enrichment configured, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " + 43 nodeDetails = "If Attributes enrichment configured, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " +
44 "with specific prefix: <i>cs/shared/ss</i>. To access those attributes in other nodes this template can be used " + 44 "with specific prefix: <i>cs/shared/ss</i>. To access those attributes in other nodes this template can be used " +
45 - "<code>meta.cs.temperature</code> or <code>meta.shared.limit</code> " + 45 + "<code>metadata.cs.temperature</code> or <code>metadata.shared.limit</code> " +
46 "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.") 46 "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.")
47 public class TbGetAttributesNode implements TbNode { 47 public class TbGetAttributesNode implements TbNode {
48 48
@@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
30 nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata", 30 nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata",
31 nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + 31 nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
32 "To access those attributes in other nodes this template can be used " + 32 "To access those attributes in other nodes this template can be used " +
33 - "<code>meta.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata") 33 + "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
34 public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> { 34 public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> {
35 35
36 @Override 36 @Override
@@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
32 "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " + 32 "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " +
33 "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + 33 "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
34 "To access those attributes in other nodes this template can be used " + 34 "To access those attributes in other nodes this template can be used " +
35 - "<code>meta.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata") 35 + "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
36 public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> { 36 public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> {
37 37
38 private TbGetRelatedAttrNodeConfiguration config; 38 private TbGetRelatedAttrNodeConfiguration config;
@@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
32 nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata", 32 nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata",
33 nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + 33 nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
34 "To access those attributes in other nodes this template can be used " + 34 "To access those attributes in other nodes this template can be used " +
35 - "<code>meta.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata") 35 + "<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata")
36 public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> { 36 public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> {
37 37
38 @Override 38 @Override
@@ -30,7 +30,7 @@ import javax.script.Bindings; @@ -30,7 +30,7 @@ import javax.script.Bindings;
30 configClazz = TbTransformMsgNodeConfiguration.class, 30 configClazz = TbTransformMsgNodeConfiguration.class,
31 nodeDescription = "Change Message payload and Metadata using JavaScript", 31 nodeDescription = "Change Message payload and Metadata using JavaScript",
32 nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside.<br/> " + 32 nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside.<br/> " +
33 - "<code>meta</code> - is a Message metadata.<br/>" + 33 + "<code>metadata</code> - is a Message metadata.<br/>" +
34 "<code>msg</code> - is a Message payload.<br/>Any properties can be changed/removed/added in those objects.") 34 "<code>msg</code> - is a Message payload.<br/>Any properties can be changed/removed/added in those objects.")
35 public class TbTransformMsgNode extends TbAbstractTransformNode { 35 public class TbTransformMsgNode extends TbAbstractTransformNode {
36 36
@@ -40,7 +40,7 @@ public class TbTransformMsgNode extends TbAbstractTransformNode { @@ -40,7 +40,7 @@ public class TbTransformMsgNode extends TbAbstractTransformNode {
40 @Override 40 @Override
41 public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { 41 public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
42 this.config = TbNodeUtils.convert(configuration, TbTransformMsgNodeConfiguration.class); 42 this.config = TbNodeUtils.convert(configuration, TbTransformMsgNodeConfiguration.class);
43 - this.jsEngine = new NashornJsEngine(config.getJsScript()); 43 + this.jsEngine = new NashornJsEngine(config.getJsScript(), "Transform");
44 setConfig(config); 44 setConfig(config);
45 } 45 }
46 46
@@ -27,7 +27,7 @@ public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguratio @@ -27,7 +27,7 @@ public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguratio
27 public TbTransformMsgNodeConfiguration defaultConfiguration() { 27 public TbTransformMsgNodeConfiguration defaultConfiguration() {
28 TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration(); 28 TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration();
29 configuration.setStartNewChain(false); 29 configuration.setStartNewChain(false);
30 - configuration.setJsScript("msg.passed = msg.passed * meta.temp; msg.bigObj.newProp = 'Ukraine' "); 30 + configuration.setJsScript("return msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine' ");
31 return configuration; 31 return configuration;
32 } 32 }
33 } 33 }
  1 +.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}
  2 +/*# sourceMappingURL=rulenode-core-config.css.map*/
1 -!function(e){function t(r){if(n[r])return n[r].exports;var u=n[r]={exports:{},id:r,loaded:!1};return e[r].call(u.exports,u,u.exports,t),u.loaded=!0,u.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}([function(e,t,n){e.exports=n(3)},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args="{{ [\'msg\'] }}" no-validate=true> </tb-js-func> </section> '},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function u(e){var t=function(t,n,r,u){var o=i.default;n.html(o),t.$watch("configuration",function(e,n){angular.equals(e,n)||u.$setViewValue(t.configuration)}),u.$render=function(){t.configuration=u.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}u.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=u;var o=n(1),i=r(o)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=n(2),o=r(u),i=n(5),a=r(i);t.default=angular.module("thingsboard.ruleChain.config",[]).directive("tbFilterNodeScriptConfig",o.default).config(a.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function u(e,t){(0,i.default)(t);for(var n in t){var r=t[n];e.translations(n,r)}}u.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=u;var o=n(4),i=r(o)}]); 1 +!function(e){function t(s){if(a[s])return a[s].exports;var n=a[s]={exports:{},id:s,loaded:!1};return e[s].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var a={};return t.m=e,t.c=a,t.p="/static/",t(0)}([function(e,t,a){e.exports=a(8)},function(e,t){},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:&apos;...&apos;}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e,t,a){var s=function(s,n,r,l){function u(){if(l.$viewValue){for(var e=[],t=0;t<s.messageTypes.length;t++)e.push(s.messageTypes[t].value);l.$viewValue.messageTypes=e,o()}}function o(){if(s.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var c=i.default;n.html(c),s.selectedMessageType=null,s.messageTypeSearchText=null,s.ngModelCtrl=l;var d=[];for(var p in a.messageType){var m={name:a.messageType[p].name,value:a.messageType[p].value};d.push(m)}s.transformMessageTypeChip=function(e){var a,s=t("filter")(d,{name:e},!0);return a=s&&s.length?angular.copy(s[0]):{name:e,value:e}},s.messageTypesSearch=function(e){var a=e?t("filter")(d,{name:e}):d;return a.map(function(e){return e.name})},s.createMessageType=function(e,t){var a=angular.element(t,n)[0].firstElementChild,s=angular.element(a),r=s.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),s.scope().$mdChipsCtrl.appendChip(r.trim()),s.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var n=0;n<e.messageTypes.length;n++){var r=e.messageTypes[n];a.messageType[r]?t.push(angular.copy(a.messageType[r])):t.push({name:r,value:r})}s.messageTypes=t,s.$watch("messageTypes",function(e,t){angular.equals(e,t)||u()},!0)},e(n.contents())(s)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:s}}n.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n,a(1);var r=a(2),i=s(r)},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,s,n){var r=i.default;a.html(r),t.$watch("configuration",function(e,a){angular.equals(e,a)||n.$setViewValue(t.configuration)}),n.$render=function(){t.configuration=n.$viewValue},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var r=a(3),i=s(r)},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e){var t=function(t,a,s,n){var r=i.default;a.html(r),t.$watch("configuration",function(e,a){angular.equals(e,a)||n.$setViewValue(t.configuration)}),n.$render=function(){t.configuration=n.$viewValue},e(a.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var r=a(4),i=s(r)},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var n=a(11),r=s(n),i=a(6),l=s(i),u=a(5),o=s(u),c=a(7),d=s(c),p=a(10),m=s(p);t.default=angular.module("thingsboard.ruleChain.config",[r.default]).directive("tbFilterNodeScriptConfig",l.default).directive("tbFilterNodeMessageTypeConfig",o.default).directive("tbFilterNodeSwitchConfig",d.default).config(m.default).name},function(e,t){"use strict";function a(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required."}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=a},function(e,t,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function n(e,t){(0,i.default)(t);for(var a in t){var s=t[a];e.translations(a,s)}}n.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n;var r=a(9),i=s(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES:{name:"Post attributes",value:"POST_ATTRIBUTES"},POST_TELEMETRY:{name:"Post telemetry",value:"POST_TELEMETRY"},RPC_REQUEST:{name:"RPC Request",value:"RPC_REQUEST"}}}).name}]);
2 //# sourceMappingURL=rulenode-core-config.js.map 2 //# sourceMappingURL=rulenode-core-config.js.map
@@ -51,7 +51,7 @@ public class TbJsFilterNodeTest { @@ -51,7 +51,7 @@ public class TbJsFilterNodeTest {
51 51
52 @Test 52 @Test
53 public void falseEvaluationDoNotSendMsg() throws TbNodeException { 53 public void falseEvaluationDoNotSendMsg() throws TbNodeException {
54 - initWithScript("10 > 15;"); 54 + initWithScript("return 10 > 15;");
55 TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes()); 55 TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes());
56 56
57 mockJsExecutor(); 57 mockJsExecutor();
@@ -64,7 +64,7 @@ public class TbJsFilterNodeTest { @@ -64,7 +64,7 @@ public class TbJsFilterNodeTest {
64 64
65 @Test 65 @Test
66 public void notValidMsgDataThrowsException() throws TbNodeException { 66 public void notValidMsgDataThrowsException() throws TbNodeException {
67 - initWithScript("10 > 15;"); 67 + initWithScript("return 10 > 15;");
68 TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), new byte[4]); 68 TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), new byte[4]);
69 69
70 when(ctx.getJsExecutor()).thenReturn(executor); 70 when(ctx.getJsExecutor()).thenReturn(executor);
@@ -77,7 +77,7 @@ public class TbJsFilterNodeTest { @@ -77,7 +77,7 @@ public class TbJsFilterNodeTest {
77 77
78 @Test 78 @Test
79 public void exceptionInJsThrowsException() throws TbNodeException { 79 public void exceptionInJsThrowsException() throws TbNodeException {
80 - initWithScript("meta.temp.curr < 15;"); 80 + initWithScript("return metadata.temp.curr < 15;");
81 TbMsgMetaData metaData = new TbMsgMetaData(); 81 TbMsgMetaData metaData = new TbMsgMetaData();
82 TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes()); 82 TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes());
83 mockJsExecutor(); 83 mockJsExecutor();
@@ -89,12 +89,12 @@ public class TbJsFilterNodeTest { @@ -89,12 +89,12 @@ public class TbJsFilterNodeTest {
89 89
90 @Test(expected = IllegalArgumentException.class) 90 @Test(expected = IllegalArgumentException.class)
91 public void notValidScriptThrowsException() throws TbNodeException { 91 public void notValidScriptThrowsException() throws TbNodeException {
92 - initWithScript("10 > 15 asdq out"); 92 + initWithScript("return 10 > 15 asdq out");
93 } 93 }
94 94
95 @Test 95 @Test
96 public void metadataConditionCanBeFalse() throws TbNodeException { 96 public void metadataConditionCanBeFalse() throws TbNodeException {
97 - initWithScript("meta.humidity < 15;"); 97 + initWithScript("return metadata.humidity < 15;");
98 TbMsgMetaData metaData = new TbMsgMetaData(); 98 TbMsgMetaData metaData = new TbMsgMetaData();
99 metaData.putValue("temp", "10"); 99 metaData.putValue("temp", "10");
100 metaData.putValue("humidity", "99"); 100 metaData.putValue("humidity", "99");
@@ -109,7 +109,7 @@ public class TbJsFilterNodeTest { @@ -109,7 +109,7 @@ public class TbJsFilterNodeTest {
109 109
110 @Test 110 @Test
111 public void metadataConditionCanBeTrue() throws TbNodeException { 111 public void metadataConditionCanBeTrue() throws TbNodeException {
112 - initWithScript("meta.temp < 15;"); 112 + initWithScript("return metadata.temp < 15;");
113 TbMsgMetaData metaData = new TbMsgMetaData(); 113 TbMsgMetaData metaData = new TbMsgMetaData();
114 metaData.putValue("temp", "10"); 114 metaData.putValue("temp", "10");
115 metaData.putValue("humidity", "99"); 115 metaData.putValue("humidity", "99");
@@ -123,7 +123,7 @@ public class TbJsFilterNodeTest { @@ -123,7 +123,7 @@ public class TbJsFilterNodeTest {
123 123
124 @Test 124 @Test
125 public void msgJsonParsedAndBinded() throws TbNodeException { 125 public void msgJsonParsedAndBinded() throws TbNodeException {
126 - initWithScript("msg.passed < 15 && msg.name === 'Vit' && meta.temp == 10 && msg.bigObj.prop == 42;"); 126 + initWithScript("return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 10 && msg.bigObj.prop == 42;");
127 TbMsgMetaData metaData = new TbMsgMetaData(); 127 TbMsgMetaData metaData = new TbMsgMetaData();
128 metaData.putValue("temp", "10"); 128 metaData.putValue("temp", "10");
129 metaData.putValue("humidity", "99"); 129 metaData.putValue("humidity", "99");
@@ -53,27 +53,16 @@ public class TbJsSwitchNodeTest { @@ -53,27 +53,16 @@ public class TbJsSwitchNodeTest {
53 private ListeningExecutor executor; 53 private ListeningExecutor executor;
54 54
55 @Test 55 @Test
56 - public void routeToAllDoNotEvaluatesJs() throws TbNodeException {  
57 - HashSet<String> relations = Sets.newHashSet("one", "two");  
58 - initWithScript("test qwerty", relations, true);  
59 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes());  
60 -  
61 - node.onMsg(ctx, msg);  
62 - verify(ctx).tellNext(msg, relations);  
63 - verifyNoMoreInteractions(ctx, executor);  
64 - }  
65 -  
66 - @Test  
67 public void multipleRoutesAreAllowed() throws TbNodeException { 56 public void multipleRoutesAreAllowed() throws TbNodeException {
68 - String jsCode = "function nextRelation(meta, msg) {\n" +  
69 - " if(msg.passed == 5 && meta.temp == 10)\n" + 57 + String jsCode = "function nextRelation(metadata, msg) {\n" +
  58 + " if(msg.passed == 5 && metadata.temp == 10)\n" +
70 " return ['three', 'one']\n" + 59 " return ['three', 'one']\n" +
71 " else\n" + 60 " else\n" +
72 " return 'two';\n" + 61 " return 'two';\n" +
73 "};\n" + 62 "};\n" +
74 "\n" + 63 "\n" +
75 - "nextRelation(meta, msg);";  
76 - initWithScript(jsCode, Sets.newHashSet("one", "two", "three"), false); 64 + "return nextRelation(metadata, msg);";
  65 + initWithScript(jsCode);
77 TbMsgMetaData metaData = new TbMsgMetaData(); 66 TbMsgMetaData metaData = new TbMsgMetaData();
78 metaData.putValue("temp", "10"); 67 metaData.putValue("temp", "10");
79 metaData.putValue("humidity", "99"); 68 metaData.putValue("humidity", "99");
@@ -89,15 +78,15 @@ public class TbJsSwitchNodeTest { @@ -89,15 +78,15 @@ public class TbJsSwitchNodeTest {
89 78
90 @Test 79 @Test
91 public void allowedRelationPassed() throws TbNodeException { 80 public void allowedRelationPassed() throws TbNodeException {
92 - String jsCode = "function nextRelation(meta, msg) {\n" +  
93 - " if(msg.passed == 5 && meta.temp == 10)\n" + 81 + String jsCode = "function nextRelation(metadata, msg) {\n" +
  82 + " if(msg.passed == 5 && metadata.temp == 10)\n" +
94 " return 'one'\n" + 83 " return 'one'\n" +
95 " else\n" + 84 " else\n" +
96 " return 'two';\n" + 85 " return 'two';\n" +
97 "};\n" + 86 "};\n" +
98 "\n" + 87 "\n" +
99 - "nextRelation(meta, msg);";  
100 - initWithScript(jsCode, Sets.newHashSet("one", "two"), false); 88 + "return nextRelation(metadata, msg);";
  89 + initWithScript(jsCode);
101 TbMsgMetaData metaData = new TbMsgMetaData(); 90 TbMsgMetaData metaData = new TbMsgMetaData();
102 metaData.putValue("temp", "10"); 91 metaData.putValue("temp", "10");
103 metaData.putValue("humidity", "99"); 92 metaData.putValue("humidity", "99");
@@ -111,32 +100,9 @@ public class TbJsSwitchNodeTest { @@ -111,32 +100,9 @@ public class TbJsSwitchNodeTest {
111 verify(ctx).tellNext(msg, Sets.newHashSet("one")); 100 verify(ctx).tellNext(msg, Sets.newHashSet("one"));
112 } 101 }
113 102
114 - @Test  
115 - public void unknownRelationThrowsException() throws TbNodeException {  
116 - String jsCode = "function nextRelation(meta, msg) {\n" +  
117 - " return ['one','nine'];" +  
118 - "};\n" +  
119 - "\n" +  
120 - "nextRelation(meta, msg);";  
121 - initWithScript(jsCode, Sets.newHashSet("one", "two"), false);  
122 - TbMsgMetaData metaData = new TbMsgMetaData();  
123 - metaData.putValue("temp", "10");  
124 - metaData.putValue("humidity", "99");  
125 - String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";  
126 -  
127 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes());  
128 - mockJsExecutor();  
129 -  
130 - node.onMsg(ctx, msg);  
131 - verify(ctx).getJsExecutor();  
132 - verifyError(msg, "Unsupported relation for switch [nine, one]", IllegalStateException.class);  
133 - }  
134 -  
135 - private void initWithScript(String script, Set<String> relations, boolean routeToAll) throws TbNodeException { 103 + private void initWithScript(String script) throws TbNodeException {
136 TbJsSwitchNodeConfiguration config = new TbJsSwitchNodeConfiguration(); 104 TbJsSwitchNodeConfiguration config = new TbJsSwitchNodeConfiguration();
137 config.setJsScript(script); 105 config.setJsScript(script);
138 - config.setAllowedRelations(relations);  
139 - config.setRouteToAllWithNoCheck(routeToAll);  
140 ObjectMapper mapper = new ObjectMapper(); 106 ObjectMapper mapper = new ObjectMapper();
141 TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); 107 TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
142 108
@@ -51,7 +51,7 @@ public class TbTransformMsgNodeTest { @@ -51,7 +51,7 @@ public class TbTransformMsgNodeTest {
51 51
52 @Test 52 @Test
53 public void metadataCanBeUpdated() throws TbNodeException { 53 public void metadataCanBeUpdated() throws TbNodeException {
54 - initWithScript("meta.temp = meta.temp * 10;"); 54 + initWithScript("return metadata.temp = metadata.temp * 10;");
55 TbMsgMetaData metaData = new TbMsgMetaData(); 55 TbMsgMetaData metaData = new TbMsgMetaData();
56 metaData.putValue("temp", "7"); 56 metaData.putValue("temp", "7");
57 metaData.putValue("humidity", "99"); 57 metaData.putValue("humidity", "99");
@@ -70,7 +70,7 @@ public class TbTransformMsgNodeTest { @@ -70,7 +70,7 @@ public class TbTransformMsgNodeTest {
70 70
71 @Test 71 @Test
72 public void metadataCanBeAdded() throws TbNodeException { 72 public void metadataCanBeAdded() throws TbNodeException {
73 - initWithScript("meta.newAttr = meta.humidity - msg.passed;"); 73 + initWithScript("return metadata.newAttr = metadata.humidity - msg.passed;");
74 TbMsgMetaData metaData = new TbMsgMetaData(); 74 TbMsgMetaData metaData = new TbMsgMetaData();
75 metaData.putValue("temp", "7"); 75 metaData.putValue("temp", "7");
76 metaData.putValue("humidity", "99"); 76 metaData.putValue("humidity", "99");
@@ -89,7 +89,7 @@ public class TbTransformMsgNodeTest { @@ -89,7 +89,7 @@ public class TbTransformMsgNodeTest {
89 89
90 @Test 90 @Test
91 public void payloadCanBeUpdated() throws TbNodeException { 91 public void payloadCanBeUpdated() throws TbNodeException {
92 - initWithScript("msg.passed = msg.passed * meta.temp; msg.bigObj.newProp = 'Ukraine' "); 92 + initWithScript("return msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine' ");
93 TbMsgMetaData metaData = new TbMsgMetaData(); 93 TbMsgMetaData metaData = new TbMsgMetaData();
94 metaData.putValue("temp", "7"); 94 metaData.putValue("temp", "7");
95 metaData.putValue("humidity", "99"); 95 metaData.putValue("humidity", "99");
@@ -32,7 +32,6 @@ const forwardPort = 8080; @@ -32,7 +32,6 @@ const forwardPort = 8080;
32 32
33 const ruleNodeUiforwardHost = 'localhost'; 33 const ruleNodeUiforwardHost = 'localhost';
34 const ruleNodeUiforwardPort = 8080; 34 const ruleNodeUiforwardPort = 8080;
35 -//const ruleNodeUiforwardPort = 5000;  
36 35
37 const app = express(); 36 const app = express();
38 const server = http.createServer(app); 37 const server = http.createServer(app);
@@ -84,17 +84,32 @@ function JsonObjectEdit($compile, $templateCache, $document, toast, utils) { @@ -84,17 +84,32 @@ function JsonObjectEdit($compile, $templateCache, $document, toast, utils) {
84 scope.$watch('contentBody', function (newVal, prevVal) { 84 scope.$watch('contentBody', function (newVal, prevVal) {
85 if (!angular.equals(newVal, prevVal)) { 85 if (!angular.equals(newVal, prevVal)) {
86 var object = scope.validate(); 86 var object = scope.validate();
87 - ngModelCtrl.$setViewValue(object); 87 + if (scope.objectValid) {
  88 + if (object == null) {
  89 + scope.object = null;
  90 + } else {
  91 + if (scope.object == null) {
  92 + scope.object = {};
  93 + }
  94 + Object.keys(scope.object).forEach(function (key) {
  95 + delete scope.object[key];
  96 + });
  97 + Object.keys(object).forEach(function (key) {
  98 + scope.object[key] = object[key];
  99 + });
  100 + }
  101 + ngModelCtrl.$setViewValue(scope.object);
  102 + }
88 scope.updateValidity(); 103 scope.updateValidity();
89 } 104 }
90 }); 105 });
91 106
92 ngModelCtrl.$render = function () { 107 ngModelCtrl.$render = function () {
93 - var object = ngModelCtrl.$viewValue; 108 + scope.object = ngModelCtrl.$viewValue;
94 var content = ''; 109 var content = '';
95 try { 110 try {
96 - if (object) {  
97 - content = angular.toJson(object, true); 111 + if (scope.object) {
  112 + content = angular.toJson(scope.object, true);
98 } 113 }
99 } catch (e) { 114 } catch (e) {
100 // 115 //
@@ -1171,6 +1171,7 @@ export default angular.module('thingsboard.locale', []) @@ -1171,6 +1171,7 @@ export default angular.module('thingsboard.locale', [])
1171 "debug-mode": "Debug mode" 1171 "debug-mode": "Debug mode"
1172 }, 1172 },
1173 "rulenode": { 1173 "rulenode": {
  1174 + "details": "Details",
1174 "add": "Add rule node", 1175 "add": "Add rule node",
1175 "name": "Name", 1176 "name": "Name",
1176 "name-required": "Name is required.", 1177 "name-required": "Name is required.",
@@ -256,6 +256,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, @@ -256,6 +256,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
256 vm.isEditingRuleNodeLink = true; 256 vm.isEditingRuleNodeLink = true;
257 vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge); 257 vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
258 vm.editingRuleNodeLink = angular.copy(edge); 258 vm.editingRuleNodeLink = angular.copy(edge);
  259 + $mdUtil.nextTick(() => {
  260 + vm.ruleNodeLinkForm.$setPristine();
  261 + });
259 } 262 }
260 }, 263 },
261 nodeCallbacks: { 264 nodeCallbacks: {
@@ -266,6 +269,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, @@ -266,6 +269,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
266 vm.isEditingRuleNode = true; 269 vm.isEditingRuleNode = true;
267 vm.editingRuleNodeIndex = vm.ruleChainModel.nodes.indexOf(node); 270 vm.editingRuleNodeIndex = vm.ruleChainModel.nodes.indexOf(node);
268 vm.editingRuleNode = angular.copy(node); 271 vm.editingRuleNode = angular.copy(node);
  272 + $mdUtil.nextTick(() => {
  273 + vm.ruleNodeForm.$setPristine();
  274 + });
269 } 275 }
270 } 276 }
271 }, 277 },
@@ -65,7 +65,8 @@ @@ -65,7 +65,8 @@
65 </div> 65 </div>
66 <tb-details-sidenav class="tb-rulenode-details-sidenav" 66 <tb-details-sidenav class="tb-rulenode-details-sidenav"
67 header-title="{{vm.editingRuleNode.name}}" 67 header-title="{{vm.editingRuleNode.name}}"
68 - header-subtitle="{{'rulenode.rulenode-details' | translate}}" 68 + header-subtitle="{{(vm.types.ruleNodeType[vm.editingRuleNode.component.type].name | translate)
  69 + + ' - ' + vm.editingRuleNode.component.name}}"
69 is-read-only="false" 70 is-read-only="false"
70 is-open="vm.isEditingRuleNode" 71 is-open="vm.isEditingRuleNode"
71 is-always-edit="true" 72 is-always-edit="true"
@@ -76,16 +77,20 @@ @@ -76,16 +77,20 @@
76 <details-buttons tb-help="vm.helpLinkIdForRuleNodeType()" help-container-id="help-container"> 77 <details-buttons tb-help="vm.helpLinkIdForRuleNodeType()" help-container-id="help-container">
77 <div id="help-container"></div> 78 <div id="help-container"></div>
78 </details-buttons> 79 </details-buttons>
79 - <form name="vm.ruleNodeForm" ng-if="vm.isEditingRuleNode">  
80 - <tb-rule-node  
81 - rule-node="vm.editingRuleNode"  
82 - rule-chain-id="vm.ruleChain.id.id"  
83 - is-edit="true"  
84 - is-read-only="false"  
85 - on-delete-rule-node="vm.deleteRuleNode(event, vm.editingRuleNode)"  
86 - the-form="vm.ruleNodeForm">  
87 - </tb-rule-node>  
88 - </form> 80 + <md-tabs id="ruleNodeTabs" md-border-bottom flex class="tb-absolute-fill">
  81 + <md-tab label="{{ 'rulenode.details' | translate }}">
  82 + <form name="vm.ruleNodeForm" ng-if="vm.isEditingRuleNode">
  83 + <tb-rule-node
  84 + rule-node="vm.editingRuleNode"
  85 + rule-chain-id="vm.ruleChain.id.id"
  86 + is-edit="true"
  87 + is-read-only="false"
  88 + on-delete-rule-node="vm.deleteRuleNode(event, vm.editingRuleNode)"
  89 + the-form="vm.ruleNodeForm">
  90 + </tb-rule-node>
  91 + </form>
  92 + </md-tab>
  93 + </md-tabs>
89 </tb-details-sidenav> 94 </tb-details-sidenav>
90 <tb-details-sidenav class="tb-rulenode-link-details-sidenav" 95 <tb-details-sidenav class="tb-rulenode-link-details-sidenav"
91 header-title="{{vm.editingRuleNodeLink.label}}" 96 header-title="{{vm.editingRuleNodeLink.label}}"
@@ -38,10 +38,15 @@ export default function RuleNodeConfigDirective($compile, $templateCache, $injec @@ -38,10 +38,15 @@ export default function RuleNodeConfigDirective($compile, $templateCache, $injec
38 }; 38 };
39 39
40 scope.useDefinedDirective = function() { 40 scope.useDefinedDirective = function() {
41 - return scope.nodeDefinition.configDirective && !scope.definedDirectiveError; 41 + return scope.nodeDefinition &&
  42 + scope.nodeDefinition.configDirective && !scope.definedDirectiveError;
42 }; 43 };
43 44
44 - validateDefinedDirective(); 45 + scope.$watch('nodeDefinition', () => {
  46 + if (scope.nodeDefinition) {
  47 + validateDefinedDirective();
  48 + }
  49 + });
45 50
46 function validateDefinedDirective() { 51 function validateDefinedDirective() {
47 if (scope.nodeDefinition.uiResourceLoadError && scope.nodeDefinition.uiResourceLoadError.length) { 52 if (scope.nodeDefinition.uiResourceLoadError && scope.nodeDefinition.uiResourceLoadError.length) {
@@ -36,10 +36,14 @@ export default function RuleNodeDefinedConfigDirective($compile) { @@ -36,10 +36,14 @@ export default function RuleNodeDefinedConfigDirective($compile) {
36 }; 36 };
37 37
38 function loadTemplate() { 38 function loadTemplate() {
  39 + if (scope.ruleNodeConfigScope) {
  40 + scope.ruleNodeConfigScope.$destroy();
  41 + }
39 var directive = snake_case(attrs.ruleNodeDirective, '-'); 42 var directive = snake_case(attrs.ruleNodeDirective, '-');
40 var template = `<${directive} ng-model="configuration" ng-required="required" ng-readonly="readonly"></${directive}>`; 43 var template = `<${directive} ng-model="configuration" ng-required="required" ng-readonly="readonly"></${directive}>`;
41 element.html(template); 44 element.html(template);
42 - $compile(element.contents())(scope); 45 + scope.ruleNodeConfigScope = scope.$new();
  46 + $compile(element.contents())(scope.ruleNodeConfigScope);
43 } 47 }
44 48
45 function snake_case(name, separator) { 49 function snake_case(name, separator) {
@@ -21,33 +21,26 @@ @@ -21,33 +21,26 @@
21 21
22 <md-content class="md-padding tb-rulenode" layout="column"> 22 <md-content class="md-padding tb-rulenode" layout="column">
23 <fieldset ng-disabled="$root.loading || !isEdit || isReadOnly"> 23 <fieldset ng-disabled="$root.loading || !isEdit || isReadOnly">
24 - <md-input-container class="md-block">  
25 - <label translate>rulenode.type</label>  
26 - <input readonly name="type" ng-model="ruleNode.component.name">  
27 - </md-input-container>  
28 <section ng-if="ruleNode.component.type != types.ruleNodeType.RULE_CHAIN.value"> 24 <section ng-if="ruleNode.component.type != types.ruleNodeType.RULE_CHAIN.value">
29 - <md-input-container class="md-block">  
30 - <label translate>rulenode.name</label>  
31 - <input required name="name" ng-model="ruleNode.name">  
32 - <div ng-messages="theForm.name.$error">  
33 - <div translate ng-message="required">rulenode.name-required</div>  
34 - </div>  
35 - </md-input-container>  
36 - <md-input-container class="md-block">  
37 - <md-checkbox ng-disabled="$root.loading || !isEdit" aria-label="{{ 'rulenode.debug-mode' | translate }}"  
38 - ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }}  
39 - </md-checkbox>  
40 - </md-input-container> 25 + <section layout="column" layout-gt-sm="row">
  26 + <md-input-container flex class="md-block">
  27 + <label translate>rulenode.name</label>
  28 + <input required name="name" ng-model="ruleNode.name">
  29 + <div ng-messages="theForm.name.$error">
  30 + <div translate ng-message="required">rulenode.name-required</div>
  31 + </div>
  32 + </md-input-container>
  33 + <md-input-container class="md-block">
  34 + <md-checkbox ng-disabled="$root.loading || !isEdit" aria-label="{{ 'rulenode.debug-mode' | translate }}"
  35 + ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }}
  36 + </md-checkbox>
  37 + </md-input-container>
  38 + </section>
41 <tb-rule-node-config ng-model="ruleNode.configuration" 39 <tb-rule-node-config ng-model="ruleNode.configuration"
42 ng-required="true" 40 ng-required="true"
43 node-definition="ruleNode.component.configurationDescriptor.nodeDefinition" 41 node-definition="ruleNode.component.configurationDescriptor.nodeDefinition"
44 ng-readonly="$root.loading || !isEdit || isReadOnly"> 42 ng-readonly="$root.loading || !isEdit || isReadOnly">
45 </tb-rule-node-config> 43 </tb-rule-node-config>
46 - <!--tb-json-object-edit class="tb-rule-node-configuration-json" ng-model="ruleNode.configuration"  
47 - label="{{ 'rulenode.configuration' | translate }}"  
48 - ng-required="true"  
49 - fill-height="true">  
50 - </tb-json-object-edit-->  
51 <md-input-container class="md-block"> 44 <md-input-container class="md-block">
52 <label translate>rulenode.description</label> 45 <label translate>rulenode.description</label>
53 <textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea> 46 <textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea>