thingsboard-memoizeit

Details

diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index 2a4ba71..d1c2825 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -71,6 +71,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.UUID;
 
 import static org.thingsboard.server.dao.service.Validator.validateId;
@@ -480,6 +481,15 @@ public abstract class BaseController {
         }
     }
 
+    List<ComponentDescriptor> checkComponentDescriptorsByTypes(Set<ComponentType> types) throws ThingsboardException {
+        try {
+            log.debug("[{}] Lookup component descriptors", types);
+            return componentDescriptorService.getComponents(types);
+        } catch (Exception e) {
+            throw handleException(e, false);
+        }
+    }
+
     List<ComponentDescriptor> checkPluginActionsByPluginClazz(String pluginClazz) throws ThingsboardException {
         try {
             checkComponentDescriptorByClazz(pluginClazz);
diff --git a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
index e63a443..7a43cd3 100644
--- a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
@@ -21,7 +21,9 @@ import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
 import org.thingsboard.server.common.data.plugin.ComponentType;
 import org.thingsboard.server.exception.ThingsboardException;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 @RestController
 @RequestMapping("/api")
@@ -52,6 +54,22 @@ public class ComponentDescriptorController extends BaseController {
     }
 
     @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
+    @RequestMapping(value = "/components", params = {"componentTypes"}, method = RequestMethod.GET)
+    @ResponseBody
+    public List<ComponentDescriptor> getComponentDescriptorsByTypes(@RequestParam("componentTypes") String[] strComponentTypes) throws ThingsboardException {
+        checkArrayParameter("componentTypes", strComponentTypes);
+        try {
+            Set<ComponentType> componentTypes = new HashSet<>();
+            for (String strComponentType : strComponentTypes) {
+                componentTypes.add(ComponentType.valueOf(strComponentType));
+            }
+            return checkComponentDescriptorsByTypes(componentTypes);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
     @RequestMapping(value = "/components/actions/{pluginClazz:.+}", method = RequestMethod.GET)
     @ResponseBody
     public List<ComponentDescriptor> getPluginActionsByPluginClazz(@PathVariable("pluginClazz") String pluginClazz) throws ThingsboardException {
diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
index 910b459..d9416d7 100644
--- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
@@ -204,6 +204,15 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
     }
 
     @Override
+    public List<ComponentDescriptor> getComponents(Set<ComponentType> types) {
+        List<ComponentDescriptor> result = new ArrayList<>();
+        for (ComponentType type : types) {
+            result.addAll(componentsMap.get(type));
+        }
+        return Collections.unmodifiableList(result);
+    }
+
+    @Override
     public Optional<ComponentDescriptor> getComponent(String clazz) {
         return Optional.ofNullable(components.get(clazz));
     }
diff --git a/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java
index ea27e60..7a15a1b 100644
--- a/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java
@@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
 
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 
 /**
  * @author Andrew Shvayka
@@ -30,6 +31,8 @@ public interface ComponentDiscoveryService {
 
     List<ComponentDescriptor> getComponents(ComponentType type);
 
+    List<ComponentDescriptor> getComponents(Set<ComponentType> types);
+
     Optional<ComponentDescriptor> getComponent(String clazz);
 
     List<ComponentDescriptor> getPluginActions(String pluginClazz);
diff --git a/ui/src/app/api/component-descriptor.service.js b/ui/src/app/api/component-descriptor.service.js
index 4478d71..cc3f710 100644
--- a/ui/src/app/api/component-descriptor.service.js
+++ b/ui/src/app/api/component-descriptor.service.js
@@ -26,7 +26,8 @@ function ComponentDescriptorService($http, $q) {
     var service = {
         getComponentDescriptorsByType: getComponentDescriptorsByType,
         getComponentDescriptorByClazz: getComponentDescriptorByClazz,
-        getPluginActionsByPluginClazz: getPluginActionsByPluginClazz
+        getPluginActionsByPluginClazz: getPluginActionsByPluginClazz,
+        getComponentDescriptorsByTypes: getComponentDescriptorsByTypes
     }
 
     return service;
@@ -52,6 +53,41 @@ function ComponentDescriptorService($http, $q) {
         return deferred.promise;
     }
 
+    function getComponentDescriptorsByTypes(componentTypes) {
+        var deferred = $q.defer();
+        var result = [];
+        for (var i=componentTypes.length-1;i>=0;i--) {
+            var componentType = componentTypes[i];
+            if (componentsByType[componentType]) {
+                result = result.concat(componentsByType[componentType]);
+                componentTypes.splice(i, 1);
+            }
+        }
+        if (!componentTypes.length) {
+            deferred.resolve(result);
+        } else {
+            var url = '/api/components?componentTypes=' + componentTypes.join(',');
+            $http.get(url, null).then(function success(response) {
+                var components = response.data;
+                for (var i = 0; i < components.length; i++) {
+                    var component = components[i];
+                    var componentsList = componentsByType[component.type];
+                    if (!componentsList) {
+                        componentsList = [];
+                        componentsByType[component.type] = componentsList;
+                    }
+                    componentsList.push(component);
+                    componentsByClazz[component.clazz] = component;
+                }
+                result = result.concat(components);
+                deferred.resolve(components);
+            }, function fail() {
+                deferred.reject();
+            });
+        }
+        return deferred.promise;
+    }
+
     function getComponentDescriptorByClazz(componentDescriptorClazz) {
         var deferred = $q.defer();
         if (componentsByClazz[componentDescriptorClazz]) {
diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js
index be8e99d..f175535 100644
--- a/ui/src/app/api/rule-chain.service.js
+++ b/ui/src/app/api/rule-chain.service.js
@@ -17,9 +17,9 @@ export default angular.module('thingsboard.api.ruleChain', [])
     .factory('ruleChainService', RuleChainService).name;
 
 /*@ngInject*/
-function RuleChainService($http, $q, $filter, types) {
+function RuleChainService($http, $q, $filter, types, componentDescriptorService) {
 
-    var ruleNodeTypes = null;
+    var ruleNodeComponents = null;
 
     var service = {
         getSystemRuleChains: getSystemRuleChains,
@@ -30,8 +30,8 @@ function RuleChainService($http, $q, $filter, types) {
         deleteRuleChain: deleteRuleChain,
         getRuleChainMetaData: getRuleChainMetaData,
         saveRuleChainMetaData: saveRuleChainMetaData,
-        getRuleNodeTypes: getRuleNodeTypes,
-        getRuleNodeComponentType: getRuleNodeComponentType,
+        getRuleNodeComponents: getRuleNodeComponents,
+        getRuleNodeComponentByClazz: getRuleNodeComponentByClazz,
         getRuleNodeSupportedLinks: getRuleNodeSupportedLinks,
         resolveTargetRuleChains: resolveTargetRuleChains
     };
@@ -165,21 +165,18 @@ function RuleChainService($http, $q, $filter, types) {
         return deferred.promise;
     }
 
-    function getRuleNodeTypes() {
+    function getRuleNodeComponents() {
         var deferred = $q.defer();
-        if (ruleNodeTypes) {
-            deferred.resolve(ruleNodeTypes);
+        if (ruleNodeComponents) {
+            deferred.resolve(ruleNodeComponents);
         } else {
-            loadRuleNodeTypes().then(
-                (nodeTypes) => {
-                    ruleNodeTypes = nodeTypes;
-                    ruleNodeTypes.push(
-                        {
-                            nodeType: types.ruleNodeType.RULE_CHAIN.value,
-                            type: 'Rule chain'
-                        }
+            loadRuleNodeComponents().then(
+                (components) => {
+                    ruleNodeComponents = components;
+                    ruleNodeComponents.push(
+                        types.ruleChainNodeComponent
                     );
-                    deferred.resolve(ruleNodeTypes);
+                    deferred.resolve(ruleNodeComponents);
                 },
                 () => {
                     deferred.reject();
@@ -189,10 +186,10 @@ function RuleChainService($http, $q, $filter, types) {
         return deferred.promise;
     }
 
-    function getRuleNodeComponentType(type) {
-        var res = $filter('filter')(ruleNodeTypes, {type: type}, true);
+    function getRuleNodeComponentByClazz(clazz) {
+        var res = $filter('filter')(ruleNodeComponents, {clazz: clazz}, true);
         if (res && res.length) {
-            return res[0].nodeType;
+            return res[0];
         }
         return null;
     }
@@ -222,61 +219,8 @@ function RuleChainService($http, $q, $filter, types) {
         return deferred.promise;
     }
 
-    function loadRuleNodeTypes() {
-        var deferred = $q.defer();
-        deferred.resolve(
-            [
-                {
-                    nodeType: types.ruleNodeType.FILTER.value,
-                    type: 'Filter'
-                },
-                {
-                    nodeType: types.ruleNodeType.FILTER.value,
-                    type: 'Switch'
-                },
-                {
-                    nodeType: types.ruleNodeType.ENRICHMENT.value,
-                    type: 'Self'
-                },
-                {
-                    nodeType: types.ruleNodeType.ENRICHMENT.value,
-                    type: 'Tenant/Customer'
-                },
-                {
-                    nodeType: types.ruleNodeType.ENRICHMENT.value,
-                    type: 'Related Entity'
-                },
-                {
-                    nodeType: types.ruleNodeType.ENRICHMENT.value,
-                    type: 'Last Telemetry'
-                },
-                {
-                    nodeType: types.ruleNodeType.TRANSFORMATION.value,
-                    type: 'Modify'
-                },
-                {
-                    nodeType: types.ruleNodeType.TRANSFORMATION.value,
-                    type: 'New/Update'
-                },
-                {
-                    nodeType: types.ruleNodeType.ACTION.value,
-                    type: 'Telemetry'
-                },
-                {
-                    nodeType: types.ruleNodeType.ACTION.value,
-                    type: 'RPC call'
-                },
-                {
-                    nodeType: types.ruleNodeType.ACTION.value,
-                    type: 'Send email'
-                },
-                {
-                    nodeType: types.ruleNodeType.ACTION.value,
-                    type: 'Alarm'
-                }
-            ]
-        );
-        return deferred.promise;
+    function loadRuleNodeComponents() {
+        return componentDescriptorService.getComponentDescriptorsByTypes(types.ruleNodeTypeComponentTypes);
     }
 
 
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index b134bf0..2f8d152 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -457,6 +457,17 @@ export default angular.module('thingsboard.types', [])
                     clientSide: false
                 }
             },
+            ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION"],
+            ruleChainNodeComponent: {
+                type: 'RULE_CHAIN',
+                name: 'Rule chain',
+                clazz: 'tb.internal.RuleChain'
+            },
+            inputNodeComponent: {
+                type: 'INPUT',
+                name: 'Input',
+                clazz: 'tb.internal.Input'
+            },
             ruleNodeType: {
                 FILTER: {
                     value: "FILTER",
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 4c392ca..18c9330 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -1167,7 +1167,8 @@ export default angular.module('thingsboard.locale', [])
                     "select-rulechain": "Select rule chain",
                     "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.",
                     "rulechain-required": "Rule chain is required",
-                    "management": "Rules management"
+                    "management": "Rules management",
+                    "debug-mode": "Debug mode"
                 },
                 "rulenode": {
                     "add": "Add rule node",
@@ -1177,6 +1178,7 @@ export default angular.module('thingsboard.locale', [])
                     "description": "Description",
                     "delete": "Delete rule node",
                     "rulenode-details": "Rule node details",
+                    "debug-mode": "Debug mode",
                     "link-details": "Rule node link details",
                     "add-link": "Add link",
                     "link-label": "Link label",
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js
index c854cfa..31886b8 100644
--- a/ui/src/app/rulechain/rulechain.controller.js
+++ b/ui/src/app/rulechain/rulechain.controller.js
@@ -54,6 +54,7 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
     };
 
     vm.ruleNodeTypesModel = {};
+    vm.ruleChainLibraryLoaded = false;
     for (var type in types.ruleNodeType) {
         if (!types.ruleNodeType[type].special) {
             vm.ruleNodeTypesModel[type] = {
@@ -141,8 +142,8 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
     vm.editCallbacks = {
         edgeDoubleClick: function (event, edge) {
             var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
-            if (sourceNode.nodeType != types.ruleNodeType.INPUT.value) {
-                ruleChainService.getRuleNodeSupportedLinks(sourceNode.type).then(
+            if (sourceNode.component.type != types.ruleNodeType.INPUT.value) {
+                ruleChainService.getRuleNodeSupportedLinks(sourceNode.component.clazz).then(
                     (labels) => {
                         vm.isEditingRuleNode = false;
                         vm.editingRuleNode = null;
@@ -156,7 +157,7 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
         },
         nodeCallbacks: {
             'doubleClick': function (event, node) {
-                if (node.nodeType != types.ruleNodeType.INPUT.value) {
+                if (node.component.type != types.ruleNodeType.INPUT.value) {
                     vm.isEditingRuleNodeLink = false;
                     vm.editingRuleNodeLink = null;
                     vm.isEditingRuleNode = true;
@@ -171,9 +172,9 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
         createEdge: function (event, edge) {
             var deferred = $q.defer();
             var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
-            if (sourceNode.nodeType == types.ruleNodeType.INPUT.value) {
+            if (sourceNode.component.type == types.ruleNodeType.INPUT.value) {
                 var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination);
-                if (destNode.nodeType == types.ruleNodeType.RULE_CHAIN.value) {
+                if (destNode.component.type == types.ruleNodeType.RULE_CHAIN.value) {
                     deferred.reject();
                 } else {
                     var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId});
@@ -183,7 +184,7 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
                     deferred.resolve(edge);
                 }
             } else {
-                ruleChainService.getRuleNodeSupportedLinks(sourceNode.type).then(
+                ruleChainService.getRuleNodeSupportedLinks(sourceNode.component.clazz).then(
                     (labels) => {
                         addRuleNodeLink(event, edge, labels).then(
                             (link) => {
@@ -209,24 +210,23 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
     loadRuleChainLibrary();
 
     function loadRuleChainLibrary() {
-        ruleChainService.getRuleNodeTypes().then(
-            (ruleNodeTypes) => {
-                for (var i=0;i<ruleNodeTypes.length;i++) {
-                    var ruleNodeType = ruleNodeTypes[i];
-                    var nodeType = ruleNodeType.nodeType;
-                    var model = vm.ruleNodeTypesModel[nodeType].model;
+        ruleChainService.getRuleNodeComponents().then(
+            (ruleNodeComponents) => {
+                for (var i=0;i<ruleNodeComponents.length;i++) {
+                    var ruleNodeComponent = ruleNodeComponents[i];
+                    var componentType = ruleNodeComponent.type;
+                    var model = vm.ruleNodeTypesModel[componentType].model;
                     var node = {
                         id: model.nodes.length,
-                        nodeType: nodeType,
-                        type: ruleNodeType.type,
+                        component: ruleNodeComponent,
                         name: '',
-                        nodeClass: vm.types.ruleNodeType[nodeType].nodeClass,
-                        icon: vm.types.ruleNodeType[nodeType].icon,
+                        nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
+                        icon: vm.types.ruleNodeType[componentType].icon,
                         x: 30,
                         y: 10+50*model.nodes.length,
                         connectors: []
                     };
-                    if (nodeType == types.ruleNodeType.RULE_CHAIN.value) {
+                    if (componentType == types.ruleNodeType.RULE_CHAIN.value) {
                         node.connectors.push(
                             {
                                 type: flowchartConstants.leftConnectorType,
@@ -249,6 +249,7 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
                     }
                     model.nodes.push(node);
                 }
+                vm.ruleChainLibraryLoaded = true;
                 prepareRuleChain();
             }
         );
@@ -273,9 +274,8 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
         vm.ruleChainModel.nodes.push(
             {
                 id: vm.nextNodeID++,
-                type: "Input",
+                component: types.inputNodeComponent,
                 name: "",
-                nodeType: types.ruleNodeType.INPUT.value,
                 nodeClass: types.ruleNodeType.INPUT.nodeClass,
                 icon: types.ruleNodeType.INPUT.icon,
                 readonly: true,
@@ -301,20 +301,20 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
         var nodes = [];
         for (var i=0;i<vm.ruleChainMetaData.nodes.length;i++) {
             var ruleNode = vm.ruleChainMetaData.nodes[i];
-            var nodeType = ruleChainService.getRuleNodeComponentType(ruleNode.type);
-            if (nodeType) {
+            var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type);
+            if (component) {
                 var node = {
                     id: vm.nextNodeID++,
                     ruleNodeId: ruleNode.id,
                     additionalInfo: ruleNode.additionalInfo,
                     configuration: ruleNode.configuration,
+                    debugMode: ruleNode.debugMode,
                     x: ruleNode.additionalInfo.layoutX,
                     y: ruleNode.additionalInfo.layoutY,
-                    type: ruleNode.type,
+                    component: component,
                     name: ruleNode.name,
-                    nodeType: nodeType,
-                    nodeClass: vm.types.ruleNodeType[nodeType].nodeClass,
-                    icon: vm.types.ruleNodeType[nodeType].icon,
+                    nodeClass: vm.types.ruleNodeType[component.type].nodeClass,
+                    icon: vm.types.ruleNodeType[component.type].icon,
                     connectors: [
                         {
                             type: flowchartConstants.leftConnectorType,
@@ -347,7 +347,7 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
 
         if (vm.ruleChainMetaData.connections) {
             for (i = 0; i < vm.ruleChainMetaData.connections.length; i++) {
-                var connection = vm.ruleChainMetaData.connections[0];
+                var connection = vm.ruleChainMetaData.connections[i];
                 var sourceNode = nodes[connection.fromIndex];
                 destNode = nodes[connection.toIndex];
                 if (sourceNode && destNode) {
@@ -379,9 +379,8 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
                             targetRuleChainId: ruleChainConnection.targetRuleChainId.id,
                             x: ruleChainConnection.additionalInfo.layoutX,
                             y: ruleChainConnection.additionalInfo.layoutY,
-                            type: 'Rule chain',
+                            component: types.ruleChainNodeComponent,
                             name: ruleChain.name,
-                            nodeType: vm.types.ruleNodeType.RULE_CHAIN.value,
                             nodeClass: vm.types.ruleNodeType.RULE_CHAIN.nodeClass,
                             icon: vm.types.ruleNodeType.RULE_CHAIN.icon,
                             connectors: [
@@ -410,7 +409,9 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
             }
         }
 
-        vm.canvasControl.adjustCanvasSize();
+        if (vm.canvasControl.adjustCanvasSize) {
+            vm.canvasControl.adjustCanvasSize();
+        }
 
         vm.isDirty = false;
 
@@ -437,15 +438,16 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
 
         for (var i=0;i<vm.ruleChainModel.nodes.length;i++) {
             var node = vm.ruleChainModel.nodes[i];
-            if (node.nodeType != types.ruleNodeType.INPUT.value && node.nodeType != types.ruleNodeType.RULE_CHAIN.value) {
+            if (node.component.type != types.ruleNodeType.INPUT.value && node.component.type != types.ruleNodeType.RULE_CHAIN.value) {
                 var ruleNode = {};
                 if (node.ruleNodeId) {
                     ruleNode.id = node.ruleNodeId;
                 }
-                ruleNode.type = node.type;
+                ruleNode.type = node.component.clazz;
                 ruleNode.name = node.name;
                 ruleNode.configuration = node.configuration;
                 ruleNode.additionalInfo = node.additionalInfo;
+                ruleNode.debugMode = node.debugMode;
                 if (!ruleNode.additionalInfo) {
                     ruleNode.additionalInfo = {};
                 }
@@ -465,9 +467,9 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
             var edge = vm.ruleChainModel.edges[i];
             var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
             var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination);
-            if (sourceNode.nodeType != types.ruleNodeType.INPUT.value) {
+            if (sourceNode.component.type != types.ruleNodeType.INPUT.value) {
                 var fromIndex = nodes.indexOf(sourceNode);
-                if (destNode.nodeType == types.ruleNodeType.RULE_CHAIN.value) {
+                if (destNode.component.type == types.ruleNodeType.RULE_CHAIN.value) {
                     var ruleChainConnection = {
                         fromIndex: fromIndex,
                         targetRuleChainId: {entityType: vm.types.entityType.rulechain, id: destNode.targetRuleChainId},
@@ -522,7 +524,7 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans
                     type: flowchartConstants.leftConnectorType
                 }
             );
-            if (ruleNode.nodeType != types.ruleNodeType.RULE_CHAIN.value) {
+            if (ruleNode.component.type != types.ruleNodeType.RULE_CHAIN.value) {
                 ruleNode.connectors.push(
                     {
                         id: vm.nextConnectorID++,
diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html
index 9f22ebc..30f2caa 100644
--- a/ui/src/app/rulechain/rulechain.tpl.html
+++ b/ui/src/app/rulechain/rulechain.tpl.html
@@ -21,7 +21,7 @@
     <section class="tb-rulechain-container" flex layout="column">
         <div class="tb-rulechain-layout" flex layout="row">
             <div class="tb-rulechain-library">
-                <md-expansion-panel-group class="tb-rulechain-library-panel-group" md-component-id="libraryPanelGroup" auto-expand="true" multiple>
+                <md-expansion-panel-group ng-if="vm.ruleChainLibraryLoaded" class="tb-rulechain-library-panel-group" md-component-id="libraryPanelGroup" auto-expand="true" multiple>
                     <md-expansion-panel md-component-id="{{typeId}}" id="{{typeId}}" ng-repeat="(typeId, typeModel) in vm.ruleNodeTypesModel">
                         <md-expansion-panel-collapsed>
                             <div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div>
diff --git a/ui/src/app/rulechain/rulechain-fieldset.tpl.html b/ui/src/app/rulechain/rulechain-fieldset.tpl.html
index a79ba35..2189daa 100644
--- a/ui/src/app/rulechain/rulechain-fieldset.tpl.html
+++ b/ui/src/app/rulechain/rulechain-fieldset.tpl.html
@@ -42,6 +42,11 @@
             </div>
         </md-input-container>
         <md-input-container class="md-block">
+            <md-checkbox ng-disabled="$root.loading || !isEdit" aria-label="{{ 'rulechain.debug-mode' | translate }}"
+                         ng-model="ruleChain.debugMode">{{ 'rulechain.debug-mode' | translate }}
+            </md-checkbox>
+        </md-input-container>
+        <md-input-container class="md-block">
             <label translate>rulechain.description</label>
             <textarea ng-model="ruleChain.additionalInfo.description" rows="2"></textarea>
         </md-input-container>
diff --git a/ui/src/app/rulechain/rulenode.directive.js b/ui/src/app/rulechain/rulenode.directive.js
index 7fa4a18..998e998 100644
--- a/ui/src/app/rulechain/rulenode.directive.js
+++ b/ui/src/app/rulechain/rulenode.directive.js
@@ -33,7 +33,7 @@ export default function RuleNodeDirective($compile, $templateCache, ruleChainSer
         };
 
         scope.$watch('ruleNode', function() {
-            if (scope.ruleNode && scope.ruleNode.nodeType == types.ruleNodeType.RULE_CHAIN.value) {
+            if (scope.ruleNode && scope.ruleNode.component.type == types.ruleNodeType.RULE_CHAIN.value) {
                 scope.params.targetRuleChainId = scope.ruleNode.targetRuleChainId;
                 watchTargetRuleChain();
             } else {
diff --git a/ui/src/app/rulechain/rulenode.tpl.html b/ui/src/app/rulechain/rulenode.tpl.html
index f1d3619..f8e08e0 100644
--- a/ui/src/app/rulechain/rulenode.tpl.html
+++ b/ui/src/app/rulechain/rulenode.tpl.html
@@ -23,7 +23,7 @@
         <md-icon aria-label="node-type-icon" flex="15"
                  class="material-icons">{{node.icon}}</md-icon>
         <div layout="column" flex="85" layout-align="center">
-            <span class="tb-node-type">{{ node.type }}</span>
+            <span class="tb-node-type">{{ node.component.name }}</span>
             <span class="tb-node-title" ng-if="node.name">{{ node.name }}</span>
         </div>
         <div class="{{flowchartConstants.leftConnectorClass}}">
diff --git a/ui/src/app/rulechain/rulenode-fieldset.tpl.html b/ui/src/app/rulechain/rulenode-fieldset.tpl.html
index 2b2faf8..0d16e45 100644
--- a/ui/src/app/rulechain/rulenode-fieldset.tpl.html
+++ b/ui/src/app/rulechain/rulenode-fieldset.tpl.html
@@ -23,9 +23,9 @@
     <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.type">
+            <input readonly name="type" ng-model="ruleNode.component.name">
         </md-input-container>
-        <section ng-if="ruleNode.nodeType != types.ruleNodeType.RULE_CHAIN.value">
+        <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">
@@ -34,11 +34,16 @@
                 </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>
+            <md-input-container class="md-block">
                 <label translate>rulenode.description</label>
                 <textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea>
             </md-input-container>
         </section>
-        <section ng-if="ruleNode.nodeType == types.ruleNodeType.RULE_CHAIN.value">
+        <section ng-if="ruleNode.component.type == types.ruleNodeType.RULE_CHAIN.value">
             <tb-entity-autocomplete the-form="theForm"
                                     ng-disabled="$root.loading || !isEdit || isReadOnly"
                                     tb-required="true"