thingsboard-aplcache

Ability to change Root rule chain.

5/15/2018 11:47:00 AM

Changes

Details

diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
index 2e8955b..0508123 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
@@ -85,21 +85,6 @@ class DefaultTbContext implements TbContext {
     }
 
     @Override
-    public void tellOthers(TbMsg msg) {
-        throw new RuntimeException("Not Implemented!");
-    }
-
-    @Override
-    public void tellSibling(TbMsg msg, ServerAddress address) {
-        throw new RuntimeException("Not Implemented!");
-    }
-
-    @Override
-    public void ack(TbMsg msg) {
-
-    }
-
-    @Override
     public void tellError(TbMsg msg, Throwable th) {
         if (nodeCtx.getSelf().isDebugMode()) {
             mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, "", th);
@@ -119,7 +104,7 @@ class DefaultTbContext implements TbContext {
 
     @Override
     public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
-        return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L);
+        return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), data, origMsg.getRuleChainId(), origMsg.getRuleNodeId(), 0L);
     }
 
     @Override
diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java
index d4a1f34..295bfb9 100644
--- a/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java
+++ b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java
@@ -67,7 +67,7 @@ public abstract class EntityActorsManager<T extends EntityId, A extends UntypedA
         }
     }
 
-    protected void visit(M entity, ActorRef actorRef) {}
+    public void visit(M entity, ActorRef actorRef) {}
 
     public ActorRef getOrCreateActor(ActorContext context, T entityId) {
         return actors.computeIfAbsent(entityId, eId ->
diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java
index ff0c52e..11ed5a3 100644
--- a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java
+++ b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java
@@ -49,7 +49,7 @@ public abstract class RuleChainManager extends EntityActorsManager<RuleChainId, 
     }
 
     @Override
-    protected void visit(RuleChain entity, ActorRef actorRef) {
+    public void visit(RuleChain entity, ActorRef actorRef) {
         if (entity.isRoot()) {
             rootChain = entity;
             rootChainActor = actorRef;
diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java
index a8bb069..87f1b16 100644
--- a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java
+++ b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java
@@ -20,10 +20,14 @@ import org.thingsboard.server.actors.service.DefaultActorService;
 import org.thingsboard.server.actors.shared.plugin.PluginManager;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
 import org.thingsboard.server.common.data.plugin.PluginMetaData;
 import org.thingsboard.server.common.data.rule.RuleChain;
 import org.thingsboard.server.dao.plugin.BasePluginService;
 
+import java.util.Collections;
+
 public class SystemRuleChainManager extends RuleChainManager {
 
     public SystemRuleChainManager(ActorSystemContext systemContext) {
@@ -32,7 +36,7 @@ public class SystemRuleChainManager extends RuleChainManager {
 
     @Override
     protected FetchFunction<RuleChain> getFetchEntitiesFunction() {
-        return service::findSystemRuleChains;
+        return link -> new TextPageData<>(Collections.emptyList(), link);
     }
 
     @Override
diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
index 10e247b..a3094de 100644
--- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
@@ -30,8 +30,11 @@ import org.thingsboard.server.actors.service.ContextBasedCreator;
 import org.thingsboard.server.actors.service.DefaultActorService;
 import org.thingsboard.server.actors.shared.plugin.TenantPluginManager;
 import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager;
+import org.thingsboard.server.common.data.EntityType;
 import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.RuleChainId;
 import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.rule.RuleChain;
 import org.thingsboard.server.common.msg.TbActorMsg;
 import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
 import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
@@ -130,6 +133,11 @@ public class TenantActor extends RuleChainManagerActor {
     private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
         ActorRef target = getEntityActorRef(msg.getEntityId());
         if (target != null) {
+            if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
+                RuleChain ruleChain = systemContext.getRuleChainService().
+                        findRuleChainById(new RuleChainId(msg.getEntityId().getId()));
+                ruleChainManager.visit(ruleChain, target);
+            }
             target.tell(msg, ActorRef.noSender());
         } else {
             logger.debug("Invalid component lifecycle msg: {}", msg);
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 4441fb8..3fd1698 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -558,15 +558,10 @@ public abstract class BaseController {
         SecurityUser authUser = getCurrentUser();
         TenantId tenantId = ruleChain.getTenantId();
         validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
-        if (authUser.getAuthority() != Authority.SYS_ADMIN) {
-            if (authUser.getTenantId() == null ||
-                    !tenantId.getId().equals(ModelConstants.NULL_UUID) && !authUser.getTenantId().equals(tenantId)) {
-                throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
-                        ThingsboardErrorCode.PERMISSION_DENIED);
-
-            } else if (tenantId.getId().equals(ModelConstants.NULL_UUID)) {
-                ruleChain.setConfiguration(null);
-            }
+        if (authUser.getAuthority() != Authority.TENANT_ADMIN ||
+                !authUser.getTenantId().equals(tenantId)) {
+            throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
+                    ThingsboardErrorCode.PERMISSION_DENIED);
         }
         return ruleChain;
     }
diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
index 81e5179..8619f8c 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
@@ -64,7 +64,7 @@ public class RuleChainController extends BaseController {
     @Autowired
     private EventService eventService;
 
-    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.GET)
     @ResponseBody
     public RuleChain getRuleChainById(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
@@ -77,7 +77,7 @@ public class RuleChainController extends BaseController {
         }
     }
 
-    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/ruleChain/{ruleChainId}/metadata", method = RequestMethod.GET)
     @ResponseBody
     public RuleChainMetaData getRuleChainMetaData(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
@@ -92,7 +92,7 @@ public class RuleChainController extends BaseController {
     }
 
 
-    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/ruleChain", method = RequestMethod.POST)
     @ResponseBody
     public RuleChain saveRuleChain(@RequestBody RuleChain ruleChain) throws ThingsboardException {
@@ -118,7 +118,46 @@ public class RuleChainController extends BaseController {
         }
     }
 
-    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/ruleChain/{ruleChainId}/root", method = RequestMethod.POST)
+    @ResponseBody
+    public RuleChain setRootRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
+        checkParameter(RULE_CHAIN_ID, strRuleChainId);
+        try {
+            RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
+            RuleChain ruleChain = checkRuleChain(ruleChainId);
+            TenantId tenantId = getCurrentUser().getTenantId();
+            RuleChain previousRootRuleChain = ruleChainService.getRootTenantRuleChain(tenantId);
+            if (ruleChainService.setRootRuleChain(ruleChainId)) {
+
+                previousRootRuleChain = ruleChainService.findRuleChainById(previousRootRuleChain.getId());
+
+                actorService.onEntityStateChange(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(),
+                        ComponentLifecycleEvent.UPDATED);
+
+                logEntityAction(previousRootRuleChain.getId(), previousRootRuleChain,
+                        null, ActionType.UPDATED, null);
+
+                ruleChain = ruleChainService.findRuleChainById(ruleChainId);
+
+                actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(),
+                        ComponentLifecycleEvent.UPDATED);
+
+                logEntityAction(ruleChain.getId(), ruleChain,
+                        null, ActionType.UPDATED, null);
+
+            }
+            return ruleChain;
+        } catch (Exception e) {
+            logEntityAction(emptyId(EntityType.RULE_CHAIN),
+                    null,
+                    null,
+                    ActionType.UPDATED, e, strRuleChainId);
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/ruleChain/metadata", method = RequestMethod.POST)
     @ResponseBody
     public RuleChainMetaData saveRuleChainMetaData(@RequestBody RuleChainMetaData ruleChainMetaData) throws ThingsboardException {
@@ -142,7 +181,7 @@ public class RuleChainController extends BaseController {
         }
     }
 
-    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/ruleChains", params = {"limit"}, method = RequestMethod.GET)
     @ResponseBody
     public TextPageData<RuleChain> getRuleChains(
@@ -151,48 +190,6 @@ public class RuleChainController extends BaseController {
             @RequestParam(required = false) String idOffset,
             @RequestParam(required = false) String textOffset) throws ThingsboardException {
         try {
-            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
-            if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
-                return checkNotNull(ruleChainService.findSystemRuleChains(pageLink));
-            } else {
-                TenantId tenantId = getCurrentUser().getTenantId();
-                TextPageData<RuleChain> ruleChainsData = checkNotNull(ruleChainService.findAllTenantRuleChainsByTenantIdAndPageLink(tenantId, pageLink));
-                List<RuleChain> ruleChains = ruleChainsData.getData();
-                ruleChains.stream()
-                        .filter(ruleChain -> ruleChain.getTenantId().getId().equals(ModelConstants.NULL_UUID))
-                        .forEach(ruleChain -> ruleChain.setConfiguration(null));
-                return ruleChainsData;
-            }
-        } catch (Exception e) {
-            throw handleException(e);
-        }
-    }
-
-    @PreAuthorize("hasAuthority('SYS_ADMIN')")
-    @RequestMapping(value = "/system/ruleChains", params = {"limit"}, method = RequestMethod.GET)
-    @ResponseBody
-    public TextPageData<RuleChain> getSystemRuleChains(
-            @RequestParam int limit,
-            @RequestParam(required = false) String textSearch,
-            @RequestParam(required = false) String idOffset,
-            @RequestParam(required = false) String textOffset) throws ThingsboardException {
-        try {
-            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
-            return checkNotNull(ruleChainService.findSystemRuleChains(pageLink));
-        } catch (Exception e) {
-            throw handleException(e);
-        }
-    }
-
-    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
-    @RequestMapping(value = "/tenant/ruleChains", params = {"limit"}, method = RequestMethod.GET)
-    @ResponseBody
-    public TextPageData<RuleChain> getTenantRuleChains(
-            @RequestParam int limit,
-            @RequestParam(required = false) String textSearch,
-            @RequestParam(required = false) String idOffset,
-            @RequestParam(required = false) String textOffset) throws ThingsboardException {
-        try {
             TenantId tenantId = getCurrentUser().getTenantId();
             TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
             return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink));
@@ -201,7 +198,7 @@ public class RuleChainController extends BaseController {
         }
     }
 
-    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.DELETE)
     @ResponseStatus(value = HttpStatus.OK)
     public void deleteRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
@@ -209,6 +206,7 @@ public class RuleChainController extends BaseController {
         try {
             RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
             RuleChain ruleChain = checkRuleChain(ruleChainId);
+
             ruleChainService.deleteRuleChainById(ruleChainId);
 
             actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java
index ba186cc..3b98356 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java
@@ -88,15 +88,27 @@ public abstract class CassandraAbstractDao {
     }
 
     private ResultSet execute(Statement statement, ConsistencyLevel level) {
-        log.debug("Execute cassandra statement {}", statement);
+        if (log.isDebugEnabled()) {
+            log.debug("Execute cassandra statement {}", statementToString(statement));
+        }
         return executeAsync(statement, level).getUninterruptibly();
     }
 
     private ResultSetFuture executeAsync(Statement statement, ConsistencyLevel level) {
-        log.debug("Execute cassandra async statement {}", statement);
+        if (log.isDebugEnabled()) {
+            log.debug("Execute cassandra async statement {}", statementToString(statement));
+        }
         if (statement.getConsistencyLevel() == null) {
             statement.setConsistencyLevel(level);
         }
         return new RateLimitedResultSetFuture(getSession(), rateLimiter, statement);
     }
+
+    private static String statementToString(Statement statement) {
+        if (statement instanceof BoundStatement) {
+            return ((BoundStatement)statement).preparedStatement().getQueryString();
+        } else {
+            return statement.toString();
+        }
+    }
 }
\ No newline at end of file
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
index 9ce1fbe..4a64890 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
@@ -57,8 +57,6 @@ import java.util.stream.Collectors;
 @Slf4j
 public class BaseRuleChainService extends AbstractEntityService implements RuleChainService {
 
-    public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
-
     @Autowired
     private RuleChainDao ruleChainDao;
 
@@ -71,12 +69,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
     @Override
     public RuleChain saveRuleChain(RuleChain ruleChain) {
         ruleChainValidator.validate(ruleChain);
-        if (ruleChain.getTenantId() == null) {
-            log.trace("Save system rule chain with predefined id {}", SYSTEM_TENANT);
-            ruleChain.setTenantId(SYSTEM_TENANT);
-        }
         RuleChain savedRuleChain = ruleChainDao.save(ruleChain);
-        if (ruleChain.isRoot() && ruleChain.getTenantId() != null && ruleChain.getId() == null) {
+        if (ruleChain.isRoot() && ruleChain.getId() == null) {
             try {
                 createRelation(new EntityRelation(savedRuleChain.getTenantId(), savedRuleChain.getId(),
                         EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
@@ -90,6 +84,31 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
     }
 
     @Override
+    public boolean setRootRuleChain(RuleChainId ruleChainId) {
+        RuleChain ruleChain = ruleChainDao.findById(ruleChainId.getId());
+        if (!ruleChain.isRoot()) {
+            RuleChain previousRootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId());
+            if (!previousRootRuleChain.getId().equals(ruleChain.getId())) {
+                try {
+                    deleteRelation(new EntityRelation(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(),
+                            EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
+                    previousRootRuleChain.setRoot(false);
+                    ruleChainDao.save(previousRootRuleChain);
+                    createRelation(new EntityRelation(ruleChain.getTenantId(), ruleChain.getId(),
+                            EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
+                    ruleChain.setRoot(true);
+                    ruleChainDao.save(ruleChain);
+                    return true;
+                } catch (ExecutionException | InterruptedException e) {
+                    log.warn("[{}] Failed to set root rule chain, ruleChainId: [{}]", ruleChainId);
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
     public RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData) {
         Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id.");
         RuleChain ruleChain = findRuleChainById(ruleChainMetaData.getRuleChainId());
@@ -267,13 +286,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
     }
 
     @Override
-    public TextPageData<RuleChain> findSystemRuleChains(TextPageLink pageLink) {
-        Validator.validatePageLink(pageLink, "Incorrect PageLink object for search system rule chain request.");
-        List<RuleChain> ruleChains = ruleChainDao.findRuleChainsByTenantId(SYSTEM_TENANT.getId(), pageLink);
-        return new TextPageData<>(ruleChains, pageLink);
-    }
-
-    @Override
     public TextPageData<RuleChain> findTenantRuleChains(TenantId tenantId, TextPageLink pageLink) {
         Validator.validateId(tenantId, "Incorrect tenant id for search rule chain request.");
         Validator.validatePageLink(pageLink, "Incorrect PageLink object for search rule chain request.");
@@ -282,17 +294,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
     }
 
     @Override
-    public TextPageData<RuleChain> findAllTenantRuleChainsByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink) {
-        log.trace("Executing findAllTenantRuleChainsByTenantIdAndPageLink, tenantId [{}], pageLink [{}]", tenantId, pageLink);
-        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
-        Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
-        List<RuleChain> ruleChains = ruleChainDao.findAllRuleChainsByTenantId(tenantId.getId(), pageLink);
-        return new TextPageData<>(ruleChains, pageLink);
-    }
-
-    @Override
     public void deleteRuleChainById(RuleChainId ruleChainId) {
         Validator.validateId(ruleChainId, "Incorrect rule chain id for delete request.");
+        RuleChain ruleChain = ruleChainDao.findById(ruleChainId.getId());
+        if (ruleChain != null && ruleChain.isRoot()) {
+            throw new DataValidationException("Deletion of Root Tenant Rule Chain is prohibited!");
+        }
         checkRuleNodesAndDelete(ruleChainId);
     }
 
@@ -325,6 +332,11 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
         relationService.saveRelation(relation);
     }
 
+    private void deleteRelation(EntityRelation relation) throws ExecutionException, InterruptedException {
+        log.debug("Deleting relation: {}", relation);
+        relationService.deleteRelation(relation);
+    }
+
     private DataValidator<RuleChain> ruleChainValidator =
             new DataValidator<RuleChain>() {
                 @Override
@@ -332,16 +344,17 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
                     if (StringUtils.isEmpty(ruleChain.getName())) {
                         throw new DataValidationException("Rule chain name should be specified!.");
                     }
-                    if (ruleChain.getTenantId() != null && !ruleChain.getTenantId().isNullUid()) {
-                        Tenant tenant = tenantDao.findById(ruleChain.getTenantId().getId());
-                        if (tenant == null) {
-                            throw new DataValidationException("Rule chain is referencing to non-existent tenant!");
-                        }
-                        if (ruleChain.isRoot()) {
-                            RuleChain rootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId());
-                            if (rootRuleChain != null && !rootRuleChain.getId().equals(ruleChain.getId())) {
-                                throw new DataValidationException("Another root rule chain is present in scope of current tenant!");
-                            }
+                    if (ruleChain.getTenantId() == null || ruleChain.getTenantId().isNullUid()) {
+                        throw new DataValidationException("Rule chain should be assigned to tenant!");
+                    }
+                    Tenant tenant = tenantDao.findById(ruleChain.getTenantId().getId());
+                    if (tenant == null) {
+                        throw new DataValidationException("Rule chain is referencing to non-existent tenant!");
+                    }
+                    if (ruleChain.isRoot()) {
+                        RuleChain rootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId());
+                        if (rootRuleChain != null && !rootRuleChain.getId().equals(ruleChain.getId())) {
+                            throw new DataValidationException("Another root rule chain is present in scope of current tenant!");
                         }
                     }
                 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java
index 47c8e86..56a20db 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java
@@ -60,15 +60,4 @@ public class CassandraRuleChainDao extends CassandraAbstractSearchTextDao<RuleCh
         return DaoUtil.convertDataList(ruleChainEntities);
     }
 
-    @Override
-    public List<RuleChain> findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) {
-        log.debug("Try to find all rule chains by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
-        List<RuleChainEntity> ruleChainEntities = findPageWithTextSearch(RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
-                Arrays.asList(in(ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY, Arrays.asList(NULL_UUID, tenantId))),
-                pageLink);
-
-        log.trace("Found rule chains [{}] by tenantId [{}] and pageLink [{}]", ruleChainEntities, tenantId, pageLink);
-        return DaoUtil.convertDataList(ruleChainEntities);
-    }
-
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
index b9a9932..4a7cfae 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
@@ -37,13 +37,4 @@ public interface RuleChainDao extends Dao<RuleChain> {
      */
     List<RuleChain> findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink);
 
-    /**
-     * Find all rule chains by tenantId and page link.
-     *
-     * @param tenantId the tenantId
-     * @param pageLink the page link
-     * @return the list of rule chain objects
-     */
-    List<RuleChain> findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink);
-
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
index d516e54..4dbe6c9 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
@@ -36,6 +36,8 @@ public interface RuleChainService {
 
     RuleChain saveRuleChain(RuleChain ruleChain);
 
+    boolean setRootRuleChain(RuleChainId ruleChainId);
+
     RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData);
 
     RuleChainMetaData loadRuleChainMetaData(RuleChainId ruleChainId);
@@ -54,12 +56,8 @@ public interface RuleChainService {
 
     List<EntityRelation> getRuleNodeRelations(RuleNodeId ruleNodeId);
 
-    TextPageData<RuleChain> findSystemRuleChains(TextPageLink pageLink);
-
     TextPageData<RuleChain> findTenantRuleChains(TenantId tenantId, TextPageLink pageLink);
 
-    TextPageData<RuleChain> findAllTenantRuleChainsByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink);
-
     void deleteRuleChainById(RuleChainId ruleChainId);
 
     void deleteRuleChainsByTenantId(TenantId tenantId);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
index 425369f..fb5d20a 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
@@ -63,15 +63,4 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao<RuleChainEntity, R
                         new PageRequest(0, pageLink.getLimit())));
     }
 
-    @Override
-    public List<RuleChain> findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) {
-        return DaoUtil.convertDataList(ruleChainRepository
-                .findAllTenantRuleChainsByTenantId(
-                        UUIDConverter.fromTimeUUID(tenantId),
-                        NULL_UUID_STR,
-                        Objects.toString(pageLink.getTextSearch(), ""),
-                        pageLink.getIdOffset() == null ? NULL_UUID_STR :  UUIDConverter.fromTimeUUID(pageLink.getIdOffset()),
-                        new PageRequest(0, pageLink.getLimit())));
-    }
-
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java
index 3b5e4db..58d4aac 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java
@@ -35,12 +35,4 @@ public interface RuleChainRepository extends CrudRepository<RuleChainEntity, Str
                                          @Param("idOffset") String idOffset,
                                          Pageable pageable);
 
-    @Query("SELECT rc FROM RuleChainEntity rc WHERE rc.tenantId IN (:tenantId, :nullTenantId) " +
-            "AND LOWER(rc.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
-            "AND rc.id > :idOffset ORDER BY rc.id")
-    List<RuleChainEntity> findAllTenantRuleChainsByTenantId(@Param("tenantId") String tenantId,
-                                                            @Param("nullTenantId") String nullTenantId,
-                                                            @Param("searchText") String searchText,
-                                                            @Param("idOffset") String idOffset,
-                                                            Pageable pageable);
 }
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
index fcb8912..01386de 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
@@ -48,12 +48,6 @@ public interface TbContext {
 
     void tellSelf(TbMsg msg, long delayMs);
 
-    void tellOthers(TbMsg msg);
-
-    void tellSibling(TbMsg msg, ServerAddress address);
-
-    void ack(TbMsg msg);
-
     void tellError(TbMsg msg, Throwable th);
 
     void updateSelf(RuleNode self);
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java
index b5a5982..adb861b 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java
@@ -69,13 +69,13 @@ public abstract class TbAbstractAlarmNode<C extends TbAbstractAlarmNodeConfigura
 
     protected ListenableFuture<JsonNode> buildAlarmDetails(TbContext ctx, TbMsg msg, JsonNode previousDetails) {
         return ctx.getJsExecutor().executeAsync(() -> {
-            TbMsg theMsg = msg;
+            TbMsg dummyMsg = msg;
             if (previousDetails != null) {
                 TbMsgMetaData metaData = msg.getMetaData().copy();
                 metaData.putValue(PREV_ALARM_DETAILS, mapper.writeValueAsString(previousDetails));
-                theMsg = ctx.newMsg(msg.getType(), msg.getOriginator(), metaData, msg.getData());
+                dummyMsg = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), metaData, msg.getData());
             }
-            return buildDetailsJsEngine.executeJson(theMsg);
+            return buildDetailsJsEngine.executeJson(dummyMsg);
         });
     }
 
diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js
index 821d82d..e7436de 100644
--- a/ui/src/app/api/rule-chain.service.js
+++ b/ui/src/app/api/rule-chain.service.js
@@ -22,11 +22,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
     var ruleNodeComponents = null;
 
     var service = {
-        getSystemRuleChains: getSystemRuleChains,
-        getTenantRuleChains: getTenantRuleChains,
         getRuleChains: getRuleChains,
         getRuleChain: getRuleChain,
         saveRuleChain: saveRuleChain,
+        setRootRuleChain: setRootRuleChain,
         deleteRuleChain: deleteRuleChain,
         getRuleChainMetaData: getRuleChainMetaData,
         saveRuleChainMetaData: saveRuleChainMetaData,
@@ -40,29 +39,9 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
 
     return service;
 
-    function getSystemRuleChains (pageLink, config) {
-        var deferred = $q.defer();
-        var url = '/api/system/ruleChains?limit=' + pageLink.limit;
-        if (angular.isDefined(pageLink.textSearch)) {
-            url += '&textSearch=' + pageLink.textSearch;
-        }
-        if (angular.isDefined(pageLink.idOffset)) {
-            url += '&idOffset=' + pageLink.idOffset;
-        }
-        if (angular.isDefined(pageLink.textOffset)) {
-            url += '&textOffset=' + pageLink.textOffset;
-        }
-        $http.get(url, config).then(function success(response) {
-            deferred.resolve(response.data);
-        }, function fail() {
-            deferred.reject();
-        });
-        return deferred.promise;
-    }
-
-    function getTenantRuleChains (pageLink, config) {
+    function getRuleChains (pageLink, config) {
         var deferred = $q.defer();
-        var url = '/api/tenant/ruleChains?limit=' + pageLink.limit;
+        var url = '/api/ruleChains?limit=' + pageLink.limit;
         if (angular.isDefined(pageLink.textSearch)) {
             url += '&textSearch=' + pageLink.textSearch;
         }
@@ -80,18 +59,9 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
         return deferred.promise;
     }
 
-    function getRuleChains (pageLink, config) {
+    function getRuleChain(ruleChainId, config) {
         var deferred = $q.defer();
-        var url = '/api/ruleChains?limit=' + pageLink.limit;
-        if (angular.isDefined(pageLink.textSearch)) {
-            url += '&textSearch=' + pageLink.textSearch;
-        }
-        if (angular.isDefined(pageLink.idOffset)) {
-            url += '&idOffset=' + pageLink.idOffset;
-        }
-        if (angular.isDefined(pageLink.textOffset)) {
-            url += '&textOffset=' + pageLink.textOffset;
-        }
+        var url = '/api/ruleChain/' + ruleChainId;
         $http.get(url, config).then(function success(response) {
             deferred.resolve(response.data);
         }, function fail() {
@@ -100,10 +70,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
         return deferred.promise;
     }
 
-    function getRuleChain(ruleChainId, config) {
+    function saveRuleChain(ruleChain) {
         var deferred = $q.defer();
-        var url = '/api/ruleChain/' + ruleChainId;
-        $http.get(url, config).then(function success(response) {
+        var url = '/api/ruleChain';
+        $http.post(url, ruleChain).then(function success(response) {
             deferred.resolve(response.data);
         }, function fail() {
             deferred.reject();
@@ -111,10 +81,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
         return deferred.promise;
     }
 
-    function saveRuleChain(ruleChain) {
+    function setRootRuleChain(ruleChainId) {
         var deferred = $q.defer();
-        var url = '/api/ruleChain';
-        $http.post(url, ruleChain).then(function success(response) {
+        var url = '/api/ruleChain/' + ruleChainId + '/root';
+        $http.post(url).then(function success(response) {
             deferred.resolve(response.data);
         }, function fail() {
             deferred.reject();
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index fc161e7..9d1052f 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -1163,11 +1163,15 @@ export default angular.module('thingsboard.locale', [])
                 "rulechain": {
                     "rulechain": "Rule chain",
                     "rulechains": "Rule chains",
+                    "root": "Root",
                     "delete": "Delete rule chain",
                     "name": "Name",
                     "name-required": "Name is required.",
                     "description": "Description",
                     "add": "Add Rule Chain",
+                    "set-root": "Make rule chain root",
+                    "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?",
+                    "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.",
                     "delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?",
                     "delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.",
                     "delete-rulechains-title": "Are you sure you want to delete { count, select, 1 {1 rule chain} other {# rule chains} }?",
diff --git a/ui/src/app/rulechain/rulechain.directive.js b/ui/src/app/rulechain/rulechain.directive.js
index b23cd98..8d19229 100644
--- a/ui/src/app/rulechain/rulechain.directive.js
+++ b/ui/src/app/rulechain/rulechain.directive.js
@@ -40,6 +40,7 @@ export default function RuleChainDirective($compile, $templateCache, $mdDialog, 
             isEdit: '=',
             isReadOnly: '=',
             theForm: '=',
+            onSetRootRuleChain: '&',
             onExportRuleChain: '&',
             onDeleteRuleChain: '&'
         }
diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js
index 2aefd82..f649f53 100644
--- a/ui/src/app/rulechain/rulechain.routes.js
+++ b/ui/src/app/rulechain/rulechain.routes.js
@@ -81,7 +81,7 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
                 pageTitle: 'rulechain.rulechain'
             },
             ncyBreadcrumb: {
-                label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}'
+                label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name + (vm.ruleChain.root ? (\' (\' + (\'rulechain.root\' | translate) + \')\') : \'\') }}", "translate": "false"}'
             }
     }).state('home.ruleChains.importRuleChain', {
         url: '/ruleChain/import',
diff --git a/ui/src/app/rulechain/rulechain-card.tpl.html b/ui/src/app/rulechain/rulechain-card.tpl.html
index 48a572c..a3ebfff 100644
--- a/ui/src/app/rulechain/rulechain-card.tpl.html
+++ b/ui/src/app/rulechain/rulechain-card.tpl.html
@@ -15,4 +15,4 @@
     limitations under the License.
 
 -->
-<div class="tb-uppercase" ng-if="item && parentCtl.types.id.nullUid === item.tenantId.id" translate>rulechain.system</div>
+<div ng-if="item && item.root" translate>rulechain.root</div>
diff --git a/ui/src/app/rulechain/rulechain-fieldset.tpl.html b/ui/src/app/rulechain/rulechain-fieldset.tpl.html
index 2189daa..9f786ab 100644
--- a/ui/src/app/rulechain/rulechain-fieldset.tpl.html
+++ b/ui/src/app/rulechain/rulechain-fieldset.tpl.html
@@ -18,8 +18,11 @@
 <md-button ng-click="onExportRuleChain({event: $event})"
            ng-show="!isEdit"
            class="md-raised md-primary">{{ 'rulechain.export' | translate }}</md-button>
+<md-button ng-click="onSetRootRuleChain({event: $event})"
+           ng-show="!isEdit && !ruleChain.root"
+           class="md-raised md-primary">{{ 'rulechain.set-root' | translate }}</md-button>
 <md-button ng-click="onDeleteRuleChain({event: $event})"
-           ng-show="!isEdit && !isReadOnly"
+           ng-show="!isEdit && !ruleChain.root"
            class="md-raised md-primary">{{ 'rulechain.delete' | translate }}</md-button>
 
 <div layout="row">
diff --git a/ui/src/app/rulechain/rulechains.controller.js b/ui/src/app/rulechain/rulechains.controller.js
index 7c857f2..3da5a51 100644
--- a/ui/src/app/rulechain/rulechains.controller.js
+++ b/ui/src/app/rulechain/rulechains.controller.js
@@ -21,7 +21,8 @@ import ruleChainCard from './rulechain-card.tpl.html';
 /* eslint-enable import/no-unresolved, import/default */
 
 /*@ngInject*/
-export default function RuleChainsController(ruleChainService, userService, importExport, $state, $stateParams, $filter, $translate, types) {
+export default function RuleChainsController(ruleChainService, userService, importExport, $state,
+                                             $stateParams, $filter, $translate, $mdDialog, types) {
 
     var ruleChainActionsList = [
         {
@@ -42,12 +43,21 @@ export default function RuleChainsController(ruleChainService, userService, impo
         },
         {
             onAction: function ($event, item) {
+                setRootRuleChain($event, item);
+            },
+            name: function() { return $translate.instant('rulechain.set-root') },
+            details: function() { return $translate.instant('rulechain.set-root') },
+            icon: "flag",
+            isEnabled: isNonRootRuleChain
+        },
+        {
+            onAction: function ($event, item) {
                 vm.grid.deleteItem($event, item);
             },
             name: function() { return $translate.instant('action.delete') },
             details: function() { return $translate.instant('rulechain.delete') },
             icon: "delete",
-            isEnabled: isRuleChainEditable
+            isEnabled: isNonRootRuleChain
         }
     ];
 
@@ -107,10 +117,7 @@ export default function RuleChainsController(ruleChainService, userService, impo
         addItemText: function() { return $translate.instant('rulechain.add-rulechain-text') },
         noItemsText: function() { return $translate.instant('rulechain.no-rulechains-text') },
         itemDetailsText: function() { return $translate.instant('rulechain.rulechain-details') },
-        isSelectionEnabled: isRuleChainEditable,
-        isDetailsReadOnly: function(ruleChain) {
-            return !isRuleChainEditable(ruleChain);
-        }
+        isSelectionEnabled: isNonRootRuleChain
     };
 
     if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
@@ -121,9 +128,11 @@ export default function RuleChainsController(ruleChainService, userService, impo
         vm.ruleChainGridConfig.topIndex = $stateParams.topIndex;
     }
 
-    vm.isRuleChainEditable = isRuleChainEditable;
+    vm.isRootRuleChain = isRootRuleChain;
+    vm.isNonRootRuleChain = isNonRootRuleChain;
 
     vm.exportRuleChain = exportRuleChain;
+    vm.setRootRuleChain = setRootRuleChain;
 
     function deleteRuleChainTitle(ruleChain) {
         return $translate.instant('rulechain.delete-rulechain-title', {ruleChainName: ruleChain.name});
@@ -172,12 +181,12 @@ export default function RuleChainsController(ruleChainService, userService, impo
         return ruleChain ? ruleChain.name : '';
     }
 
-    function isRuleChainEditable(ruleChain) {
-        if (userService.getAuthority() === 'TENANT_ADMIN') {
-            return ruleChain && ruleChain.tenantId.id != types.id.nullUid;
-        } else {
-            return userService.getAuthority() === 'SYS_ADMIN';
-        }
+    function isRootRuleChain(ruleChain) {
+        return ruleChain && ruleChain.root;
+    }
+
+    function isNonRootRuleChain(ruleChain) {
+        return ruleChain && !ruleChain.root;
     }
 
     function exportRuleChain($event, ruleChain) {
@@ -185,4 +194,22 @@ export default function RuleChainsController(ruleChainService, userService, impo
         importExport.exportRuleChain(ruleChain.id.id);
     }
 
+    function setRootRuleChain($event, ruleChain) {
+        $event.stopPropagation();
+        var confirm = $mdDialog.confirm()
+            .targetEvent($event)
+            .title($translate.instant('rulechain.set-root-rulechain-title', {ruleChainName: ruleChain.name}))
+            .htmlContent($translate.instant('rulechain.set-root-rulechain-text'))
+            .ariaLabel($translate.instant('rulechain.set-root'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+            ruleChainService.setRootRuleChain(ruleChain.id.id).then(
+                () => {
+                    vm.grid.refreshList();
+                }
+            );
+        });
+
+    }
 }
diff --git a/ui/src/app/rulechain/rulechains.tpl.html b/ui/src/app/rulechain/rulechains.tpl.html
index cf9d256..c780b46 100644
--- a/ui/src/app/rulechain/rulechains.tpl.html
+++ b/ui/src/app/rulechain/rulechains.tpl.html
@@ -26,8 +26,10 @@
                      is-edit="vm.grid.detailsConfig.isDetailsEditMode"
                      is-read-only="vm.grid.isDetailsReadOnly(vm.grid.operatingItem())"
                      the-form="vm.grid.detailsForm"
+                     on-set-root-rule-chain="vm.setRootRuleChain(event, vm.grid.detailsConfig.currentItem)"
                      on-export-rule-chain="vm.exportRuleChain(event, vm.grid.detailsConfig.currentItem)"
-                     on-delete-rule-chain="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-rule-chain>
+                     on-delete-rule-chain="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)">
+            </tb-rule-chain>
         </md-tab>
         <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleChainEditable(vm.grid.operatingItem())" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
             <tb-attribute-table flex