thingsboard-aplcache

Merge pull request #1132 from thingsboard/feature/entity-view-type WIP:

10/10/2018 5:30:44 AM

Changes

Details

diff --git a/application/src/main/data/upgrade/2.1.2/schema_update.cql b/application/src/main/data/upgrade/2.1.2/schema_update.cql
new file mode 100644
index 0000000..c02571a
--- /dev/null
+++ b/application/src/main/data/upgrade/2.1.2/schema_update.cql
@@ -0,0 +1,110 @@
+--
+-- Copyright © 2016-2018 The Thingsboard Authors
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_name;
+DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_search_text;
+DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer;
+DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id;
+
+DROP TABLE IF EXISTS thingsboard.entity_views;
+
+CREATE TABLE IF NOT EXISTS thingsboard.entity_view (
+    id timeuuid,
+    entity_id timeuuid,
+    entity_type text,
+    tenant_id timeuuid,
+    customer_id timeuuid,
+    name text,
+    type text,
+    keys text,
+    start_ts bigint,
+    end_ts bigint,
+    search_text text,
+    additional_info text,
+    PRIMARY KEY (id, entity_id, tenant_id, customer_id, type)
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS
+    SELECT *
+    from thingsboard.entity_view
+    WHERE tenant_id IS NOT NULL
+      AND entity_id IS NOT NULL
+      AND customer_id IS NOT NULL
+      AND type IS NOT NULL
+      AND name IS NOT NULL
+      AND id IS NOT NULL
+    PRIMARY KEY (tenant_id, name, id, customer_id, entity_id, type)
+    WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS
+    SELECT *
+    from thingsboard.entity_view
+    WHERE tenant_id IS NOT NULL
+      AND entity_id IS NOT NULL
+      AND customer_id IS NOT NULL
+      AND type IS NOT NULL
+      AND search_text IS NOT NULL
+      AND id IS NOT NULL
+    PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id, type)
+    WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_by_type_and_search_text AS
+    SELECT *
+    from thingsboard.entity_view
+    WHERE tenant_id IS NOT NULL
+      AND entity_id IS NOT NULL
+      AND customer_id IS NOT NULL
+      AND type IS NOT NULL
+      AND search_text IS NOT NULL
+      AND id IS NOT NULL
+    PRIMARY KEY (tenant_id, type, search_text, id, customer_id, entity_id)
+    WITH CLUSTERING ORDER BY (type ASC, search_text ASC, id DESC, customer_id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS
+    SELECT *
+    from thingsboard.entity_view
+    WHERE tenant_id IS NOT NULL
+      AND customer_id IS NOT NULL
+      AND entity_id IS NOT NULL
+      AND type IS NOT NULL
+      AND search_text IS NOT NULL
+      AND id IS NOT NULL
+    PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id, type)
+    WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer_and_type AS
+    SELECT *
+    from thingsboard.entity_view
+    WHERE tenant_id IS NOT NULL
+      AND customer_id IS NOT NULL
+      AND entity_id IS NOT NULL
+      AND type IS NOT NULL
+      AND search_text IS NOT NULL
+      AND id IS NOT NULL
+    PRIMARY KEY (tenant_id, type, customer_id, search_text, id, entity_id)
+    WITH CLUSTERING ORDER BY (type ASC, customer_id DESC, search_text ASC, id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS
+    SELECT *
+    from thingsboard.entity_view
+    WHERE tenant_id IS NOT NULL
+      AND customer_id IS NOT NULL
+      AND entity_id IS NOT NULL
+      AND type IS NOT NULL
+      AND search_text IS NOT NULL
+      AND id IS NOT NULL
+    PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id, type)
+    WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);
\ No newline at end of file
diff --git a/application/src/main/data/upgrade/2.1.2/schema_update.sql b/application/src/main/data/upgrade/2.1.2/schema_update.sql
new file mode 100644
index 0000000..14b717c
--- /dev/null
+++ b/application/src/main/data/upgrade/2.1.2/schema_update.sql
@@ -0,0 +1,32 @@
+--
+-- Copyright © 2016-2018 The Thingsboard Authors
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+DROP TABLE IF EXISTS entity_views;
+
+CREATE TABLE IF NOT EXISTS entity_view (
+    id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
+    entity_id varchar(31),
+    entity_type varchar(255),
+    tenant_id varchar(31),
+    customer_id varchar(31),
+    type varchar(255),
+    name varchar(255),
+    keys varchar(255),
+    start_ts bigint,
+    end_ts bigint,
+    search_text varchar(255),
+    additional_info varchar
+);
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
index 0ba35e8..a1ba1c6 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
@@ -15,6 +15,7 @@
  */
 package org.thingsboard.server.controller;
 
+import com.google.common.util.concurrent.ListenableFuture;
 import org.springframework.http.HttpStatus;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -26,6 +27,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.bind.annotation.ResponseStatus;
 import org.springframework.web.bind.annotation.RestController;
 import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.EntitySubtype;
 import org.thingsboard.server.common.data.EntityType;
 import org.thingsboard.server.common.data.EntityView;
 import org.thingsboard.server.common.data.audit.ActionType;
@@ -38,6 +40,7 @@ import org.thingsboard.server.common.data.page.TextPageData;
 import org.thingsboard.server.common.data.page.TextPageLink;
 import org.thingsboard.server.dao.exception.IncorrectParameterException;
 import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.service.security.model.SecurityUser;
 
 import java.util.List;
 import java.util.stream.Collectors;
@@ -161,6 +164,7 @@ public class EntityViewController extends BaseController {
     public TextPageData<EntityView> getCustomerEntityViews(
             @PathVariable("customerId") String strCustomerId,
             @RequestParam int limit,
+            @RequestParam(required = false) String type,
             @RequestParam(required = false) String textSearch,
             @RequestParam(required = false) String idOffset,
             @RequestParam(required = false) String textOffset) throws ThingsboardException {
@@ -170,7 +174,11 @@ public class EntityViewController extends BaseController {
             CustomerId customerId = new CustomerId(toUUID(strCustomerId));
             checkCustomerId(customerId);
             TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
-            return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+            if (type != null && type.trim().length() > 0) {
+                return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId, customerId, pageLink, type));
+            } else {
+                return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+            }
         } catch (Exception e) {
             throw handleException(e);
         }
@@ -181,13 +189,19 @@ public class EntityViewController extends BaseController {
     @ResponseBody
     public TextPageData<EntityView> getTenantEntityViews(
             @RequestParam int limit,
+            @RequestParam(required = false) String type,
             @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(entityViewService.findEntityViewByTenantId(tenantId, pageLink));
+
+            if (type != null && type.trim().length() > 0) {
+                return checkNotNull(entityViewService.findEntityViewByTenantIdAndType(tenantId, pageLink, type));
+            } else {
+                return checkNotNull(entityViewService.findEntityViewByTenantId(tenantId, pageLink));
+            }
         } catch (Exception e) {
             throw handleException(e);
         }
@@ -199,6 +213,7 @@ public class EntityViewController extends BaseController {
     public List<EntityView> findByQuery(@RequestBody EntityViewSearchQuery query) throws ThingsboardException {
         checkNotNull(query);
         checkNotNull(query.getParameters());
+        checkNotNull(query.getEntityViewTypes());
         checkEntityId(query.getParameters().getEntityId());
         try {
             List<EntityView> entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(query).get());
@@ -215,4 +230,18 @@ public class EntityViewController extends BaseController {
             throw handleException(e);
         }
     }
+
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/entityView/types", method = RequestMethod.GET)
+    @ResponseBody
+    public List<EntitySubtype> getEntityViewTypes() throws ThingsboardException {
+        try {
+            SecurityUser user = getCurrentUser();
+            TenantId tenantId = user.getTenantId();
+            ListenableFuture<List<EntitySubtype>> entityViewTypes = entityViewService.findEntityViewTypesByTenantId(tenantId);
+            return checkNotNull(entityViewTypes.get());
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
 }
diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
index da154e9..45eb66a 100644
--- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
+++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
@@ -97,6 +97,11 @@ public class ThingsboardInstallService {
 
                         databaseUpgradeService.upgradeDatabase("2.0.0");
 
+                    case "2.1.1":
+                        log.info("Upgrading ThingsBoard from version 2.1.1 to 2.1.2 ...");
+
+                        databaseUpgradeService.upgradeDatabase("2.1.1");
+
                         log.info("Updating system data...");
 
                         systemDataLoaderService.deleteSystemWidgetBundle("charts");
diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
index 934cf8d..5526953 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
@@ -39,10 +39,19 @@ import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATIO
 import static org.thingsboard.server.service.install.DatabaseHelper.CUSTOMER_ID;
 import static org.thingsboard.server.service.install.DatabaseHelper.DASHBOARD;
 import static org.thingsboard.server.service.install.DatabaseHelper.DEVICE;
+import static org.thingsboard.server.service.install.DatabaseHelper.END_TS;
+import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_ID;
+import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_TYPE;
+import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEW;
+import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEWS;
 import static org.thingsboard.server.service.install.DatabaseHelper.ID;
+import static org.thingsboard.server.service.install.DatabaseHelper.KEYS;
+import static org.thingsboard.server.service.install.DatabaseHelper.NAME;
 import static org.thingsboard.server.service.install.DatabaseHelper.SEARCH_TEXT;
+import static org.thingsboard.server.service.install.DatabaseHelper.START_TS;
 import static org.thingsboard.server.service.install.DatabaseHelper.TENANT_ID;
 import static org.thingsboard.server.service.install.DatabaseHelper.TITLE;
+import static org.thingsboard.server.service.install.DatabaseHelper.TYPE;
 
 @Service
 @NoSqlDao
@@ -213,6 +222,36 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
 
                 break;
 
+            case "2.1.1":
+
+                log.info("Upgrading Cassandra DataBase from version {} to 2.1.2 ...", fromVersion);
+
+                cluster.getSession();
+
+                ks = cluster.getCluster().getMetadata().getKeyspace(cluster.getKeyspaceName());
+
+                log.info("Dumping entity views ...");
+                Path entityViewsDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), ENTITY_VIEWS,
+                        new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, NAME, TYPE, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO},
+                        new String[]{"", "", "", "", "", "", "default", "", "0", "0", "", ""},
+                        "tb-entity-views");
+                log.info("Entity views dumped.");
+
+                log.info("Updating schema ...");
+                schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.2", SCHEMA_UPDATE_CQL);
+                loadCql(schemaUpdateFile);
+                log.info("Schema updated.");
+
+                log.info("Restoring entity views ...");
+                if (entityViewsDump != null) {
+                    CassandraDbHelper.loadCf(ks, cluster.getSession(), ENTITY_VIEW,
+                            new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, NAME, TYPE, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO}, entityViewsDump);
+                    Files.deleteIfExists(entityViewsDump);
+                }
+                log.info("Entity views restored.");
+
+                break;
+
             default:
                 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
         }
diff --git a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java b/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java
index bdf980f..7c03a71 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java
@@ -147,6 +147,8 @@ public class CassandraDbHelper {
                     str = new Double(row.getDouble(index)).toString();
                 } else if (type == DataType.cint()) {
                     str = new Integer(row.getInt(index)).toString();
+                } else if (type == DataType.bigint()) {
+                    str = new Long(row.getLong(index)).toString();
                 } else if (type == DataType.uuid()) {
                     str = row.getUUID(index).toString();
                 } else if (type == DataType.timeuuid()) {
@@ -193,6 +195,8 @@ public class CassandraDbHelper {
             boundStatement.setDouble(column, Double.valueOf(value));
         } else if (type == DataType.cint()) {
             boundStatement.setInt(column, Integer.valueOf(value));
+        } else if (type == DataType.bigint()) {
+            boundStatement.setLong(column, Long.valueOf(value));
         } else if (type == DataType.uuid()) {
             boundStatement.setUUID(column, UUID.fromString(value));
         } else if (type == DataType.timeuuid()) {
diff --git a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java
index 2f9b4a5..699eaf1 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java
@@ -45,14 +45,23 @@ public class DatabaseHelper {
     public static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
 
     public static final String DEVICE = "device";
+    public static final String ENTITY_ID = "entity_id";
     public static final String TENANT_ID = "tenant_id";
+    public static final String ENTITY_TYPE = "entity_type";
     public static final String CUSTOMER_ID = "customer_id";
     public static final String SEARCH_TEXT = "search_text";
     public static final String ADDITIONAL_INFO = "additional_info";
     public static final String ASSET = "asset";
     public static final String DASHBOARD = "dashboard";
+    public static final String ENTITY_VIEWS = "entity_views";
+    public static final String ENTITY_VIEW = "entity_view";
     public static final String ID = "id";
     public static final String TITLE = "title";
+    public static final String TYPE = "type";
+    public static final String NAME = "name";
+    public static final String KEYS = "keys";
+    public static final String START_TS = "start_ts";
+    public static final String END_TS = "end_ts";
     public static final String ASSIGNED_CUSTOMERS = "assigned_customers";
     public static final String CONFIGURATION = "configuration";
 
diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
index 7d701b7..2c7f89c 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
@@ -31,14 +31,24 @@ import java.nio.file.Paths;
 import java.sql.Connection;
 import java.sql.DriverManager;
 
+import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO;
 import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS;
 import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION;
 import static org.thingsboard.server.service.install.DatabaseHelper.CUSTOMER_ID;
 import static org.thingsboard.server.service.install.DatabaseHelper.DASHBOARD;
+import static org.thingsboard.server.service.install.DatabaseHelper.END_TS;
+import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_ID;
+import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_TYPE;
+import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEW;
+import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEWS;
 import static org.thingsboard.server.service.install.DatabaseHelper.ID;
+import static org.thingsboard.server.service.install.DatabaseHelper.KEYS;
+import static org.thingsboard.server.service.install.DatabaseHelper.NAME;
 import static org.thingsboard.server.service.install.DatabaseHelper.SEARCH_TEXT;
+import static org.thingsboard.server.service.install.DatabaseHelper.START_TS;
 import static org.thingsboard.server.service.install.DatabaseHelper.TENANT_ID;
 import static org.thingsboard.server.service.install.DatabaseHelper.TITLE;
+import static org.thingsboard.server.service.install.DatabaseHelper.TYPE;
 
 @Service
 @Profile("install")
@@ -115,6 +125,30 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
                     log.info("Schema updated.");
                 }
                 break;
+            case "2.1.1":
+                try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
+
+                    log.info("Dumping entity views ...");
+                    Path entityViewsDump = SqlDbHelper.dumpTableIfExists(conn, ENTITY_VIEWS,
+                            new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, TYPE, NAME, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO},
+                            new String[]{"", "", "", "", "", "default", "", "", "0", "0", "", ""},
+                            "tb-entity-views", true);
+                    log.info("Entity views dumped.");
+
+                    log.info("Updating schema ...");
+                    schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.2", SCHEMA_UPDATE_SQL);
+                    loadSql(schemaUpdateFile, conn);
+                    log.info("Schema updated.");
+
+                    log.info("Restoring entity views ...");
+                    if (entityViewsDump != null) {
+                        SqlDbHelper.loadTable(conn, ENTITY_VIEW,
+                                new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, TYPE, NAME, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO}, entityViewsDump, true);
+                        Files.deleteIfExists(entityViewsDump);
+                    }
+                    log.info("Entity views restored.");
+                }
+                break;
 
             default:
                 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
index ee97b97..cb45006 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
@@ -144,7 +144,9 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
 
     @Test
     public void testSaveEntityViewWithEmptyName() throws Exception {
-        doPost("/api/entityView", new EntityView())
+        EntityView entityView = new EntityView();
+        entityView.setType("default");
+        doPost("/api/entityView", entityView)
                 .andExpect(status().isBadRequest())
                 .andExpect(statusReason(containsString("Entity view name should be specified!")));
     }
@@ -355,6 +357,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
         view.setEntityId(testDevice.getId());
         view.setTenantId(savedTenant.getId());
         view.setName("Test entity view");
+        view.setType("default");
         view.setKeys(telemetry);
         view.setStartTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") * 10);
         view.setEndTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") / 10);
@@ -402,6 +405,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
         view.setEntityId(testDevice.getId());
         view.setTenantId(savedTenant.getId());
         view.setName(name);
+        view.setType("default");
         view.setKeys(telemetry);
         return doPost("/api/entityView", view, EntityView.class);
     }
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
index 25b066d..f684e2a 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
@@ -40,6 +40,7 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
     private TenantId tenantId;
     private CustomerId customerId;
     private String name;
+    private String type;
     private TelemetryEntityView keys;
     private long startTimeMs;
     private long endTimeMs;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java
index 752eafc..3bf08ea 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java
@@ -30,6 +30,7 @@ public class EntityViewSearchQuery {
 
     private RelationsSearchParameters parameters;
     private String relationType;
+    private List<String> entityViewTypes;
 
     public EntityRelationsQuery toEntitySearchQuery() {
         EntityRelationsQuery query = new EntityRelationsQuery();
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java
index a03dd89..fe1a8d1 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java
@@ -15,8 +15,13 @@
  */
 package org.thingsboard.server.dao.entityview;
 
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.ResultSetFuture;
 import com.datastax.driver.core.Statement;
 import com.datastax.driver.core.querybuilder.Select;
+import com.datastax.driver.mapping.Result;
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
@@ -30,6 +35,8 @@ import org.thingsboard.server.dao.model.nosql.EntityViewEntity;
 import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
 import org.thingsboard.server.dao.util.NoSqlDao;
 
+import javax.annotation.Nullable;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -39,14 +46,21 @@ import java.util.UUID;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
 import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_ID_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TYPE_PROPERTY;
 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN;
+import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_ENTITY_TYPE_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_TENANT_ID_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_TYPE_CF;
 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF;
 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF;
 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_NAME;
-import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_CF;
+import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_CF;
 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_NAME_PROPERTY;
 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME;
 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TYPE_PROPERTY;
 import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_PROPERTY;
 
 /**
@@ -82,7 +96,7 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
     public List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) {
         log.debug("Try to find entity views by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
         List<EntityViewEntity> entityViewEntities =
-                findPageWithTextSearch(ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                findPageWithTextSearch(ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_CF,
                 Collections.singletonList(eq(TENANT_ID_PROPERTY, tenantId)), pageLink);
         log.trace("Found entity views [{}] by tenantId [{}] and pageLink [{}]",
                 entityViewEntities, tenantId, pageLink);
@@ -90,6 +104,18 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
     }
 
     @Override
+    public List<EntityView> findEntityViewsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) {
+        log.debug("Try to find entity views by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink);
+        List<EntityViewEntity> entityViewEntities =
+                findPageWithTextSearch(ENTITY_VIEW_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_CF,
+                        Arrays.asList(eq(ENTITY_VIEW_TYPE_PROPERTY, type),
+                                eq(TENANT_ID_PROPERTY, tenantId)), pageLink);
+        log.trace("Found entity views [{}] by tenantId [{}], type [{}] and pageLink [{}]",
+                entityViewEntities, tenantId, type, pageLink);
+        return DaoUtil.convertDataList(entityViewEntities);
+    }
+
+    @Override
     public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) {
         Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_NAME).where();
         query.and(eq(ENTITY_VIEW_TENANT_ID_PROPERTY, tenantId));
@@ -111,6 +137,19 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
     }
 
     @Override
+    public List<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) {
+        log.debug("Try to find entity views by tenantId [{}], customerId[{}], type [{}] and pageLink [{}]",
+                tenantId, customerId, type, pageLink);
+        List<EntityViewEntity> entityViewEntities = findPageWithTextSearch(
+                ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_TYPE_CF,
+                Arrays.asList(eq(DEVICE_TYPE_PROPERTY, type), eq(CUSTOMER_ID_PROPERTY, customerId), eq(TENANT_ID_PROPERTY, tenantId)),
+                pageLink);
+        log.trace("Found find entity views [{}] by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]",
+                entityViewEntities, tenantId, customerId, type, pageLink);
+        return DaoUtil.convertDataList(entityViewEntities);
+    }
+
+    @Override
     public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) {
         log.debug("Try to find entity views by tenantId [{}] and entityId [{}]", tenantId, entityId);
         Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF).where();
@@ -118,4 +157,30 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
         query.and(eq(ENTITY_ID_COLUMN, entityId));
         return findListByStatementAsync(query);
     }
+
+    @Override
+    public ListenableFuture<List<EntitySubtype>> findTenantEntityViewTypesAsync(UUID tenantId) {
+        Select select = select().from(ENTITY_SUBTYPE_COLUMN_FAMILY_NAME);
+        Select.Where query = select.where();
+        query.and(eq(ENTITY_SUBTYPE_TENANT_ID_PROPERTY, tenantId));
+        query.and(eq(ENTITY_SUBTYPE_ENTITY_TYPE_PROPERTY, EntityType.ENTITY_VIEW));
+        query.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel());
+        ResultSetFuture resultSetFuture = executeAsyncRead(query);
+        return Futures.transform(resultSetFuture, new Function<ResultSet, List<EntitySubtype>>() {
+            @Nullable
+            @Override
+            public List<EntitySubtype> apply(@Nullable ResultSet resultSet) {
+                Result<EntitySubtypeEntity> result = cluster.getMapper(EntitySubtypeEntity.class).map(resultSet);
+                if (result != null) {
+                    List<EntitySubtype> entitySubtypes = new ArrayList<>();
+                    result.all().forEach((entitySubtypeEntity) ->
+                            entitySubtypes.add(entitySubtypeEntity.toEntitySubtype())
+                    );
+                    return entitySubtypes;
+                } else {
+                    return Collections.emptyList();
+                }
+            }
+        });
+    }
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java
index ba43385..8147a07 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.entityview;
 
 import com.google.common.util.concurrent.ListenableFuture;
 import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.EntitySubtype;
 import org.thingsboard.server.common.data.EntityView;
 import org.thingsboard.server.common.data.page.TextPageLink;
 import org.thingsboard.server.dao.Dao;
@@ -48,6 +49,16 @@ public interface EntityViewDao extends Dao<EntityView> {
     List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink);
 
     /**
+     * Find entity views by tenantId, type and page link.
+     *
+     * @param tenantId the tenantId
+     * @param type the type
+     * @param pageLink the page link
+     * @return the list of entity view objects
+     */
+    List<EntityView> findEntityViewsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink);
+
+    /**
      * Find entity views by tenantId and entity view name.
      *
      * @param tenantId the tenantId
@@ -68,6 +79,27 @@ public interface EntityViewDao extends Dao<EntityView> {
                                                             UUID customerId,
                                                             TextPageLink pageLink);
 
+    /**
+     * Find entity views by tenantId, customerId, type and page link.
+     *
+     * @param tenantId the tenantId
+     * @param customerId the customerId
+     * @param type the type
+     * @param pageLink the page link
+     * @return the list of entity view objects
+     */
+    List<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId,
+                                                                   UUID customerId,
+                                                                   String type,
+                                                                   TextPageLink pageLink);
 
     ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId);
+
+    /**
+     * Find tenants entity view types.
+     *
+     * @return the list of tenant entity view type objects
+     */
+    ListenableFuture<List<EntitySubtype>> findTenantEntityViewTypesAsync(UUID tenantId);
+
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
index 19f326c..da87b44 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
@@ -16,6 +16,7 @@
 package org.thingsboard.server.dao.entityview;
 
 import com.google.common.util.concurrent.ListenableFuture;
+import org.thingsboard.server.common.data.EntitySubtype;
 import org.thingsboard.server.common.data.EntityView;
 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
 import org.thingsboard.server.common.data.id.CustomerId;
@@ -44,8 +45,12 @@ public interface EntityViewService {
 
     TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink);
 
+    TextPageData<EntityView> findEntityViewByTenantIdAndType(TenantId tenantId, TextPageLink pageLink, String type);
+
     TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
 
+    TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, TextPageLink pageLink, String type);
+
     ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query);
 
     ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId);
@@ -55,4 +60,6 @@ public interface EntityViewService {
     void deleteEntityView(EntityViewId entityViewId);
 
     void deleteEntityViewsByTenantId(TenantId tenantId);
+
+    ListenableFuture<List<EntitySubtype>> findEntityViewTypesByTenantId(TenantId tenantId);
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
index 2c5ec75..6b4ffbc 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
@@ -15,6 +15,7 @@
  */
 package org.thingsboard.server.dao.entityview;
 
+import com.google.common.base.Function;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -29,6 +30,8 @@ import org.springframework.cache.annotation.Caching;
 import org.springframework.stereotype.Service;
 import org.thingsboard.server.common.data.Customer;
 import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.EntitySubtype;
 import org.thingsboard.server.common.data.EntityType;
 import org.thingsboard.server.common.data.EntityView;
 import org.thingsboard.server.common.data.Tenant;
@@ -54,6 +57,8 @@ import javax.annotation.Nullable;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
@@ -63,6 +68,7 @@ import static org.thingsboard.server.common.data.CacheConstants.RELATIONS_CACHE;
 import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
 import static org.thingsboard.server.dao.service.Validator.validateId;
 import static org.thingsboard.server.dao.service.Validator.validatePageLink;
+import static org.thingsboard.server.dao.service.Validator.validateString;
 
 /**
  * Created by Victor Basanets on 8/28/2017.
@@ -158,6 +164,16 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
     }
 
     @Override
+    public TextPageData<EntityView> findEntityViewByTenantIdAndType(TenantId tenantId, TextPageLink pageLink, String type) {
+        log.trace("Executing findEntityViewByTenantIdAndType, tenantId [{}], pageLink [{}], type [{}]", tenantId, pageLink, type);
+        validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+        validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
+        validateString(type, "Incorrect type " + type);
+        List<EntityView> entityViews = entityViewDao.findEntityViewsByTenantIdAndType(tenantId.getId(), type, pageLink);
+        return new TextPageData<>(entityViews, pageLink);
+    }
+
+    @Override
     public TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId,
                                                                            TextPageLink pageLink) {
         log.trace("Executing findEntityViewByTenantIdAndCustomerId, tenantId [{}], customerId [{}]," +
@@ -171,6 +187,19 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
     }
 
     @Override
+    public TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, TextPageLink pageLink, String type) {
+        log.trace("Executing findEntityViewsByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}]," +
+                " pageLink [{}], type [{}]", tenantId, customerId, pageLink, type);
+        validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+        validateId(customerId, INCORRECT_CUSTOMER_ID + customerId);
+        validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
+        validateString(type, "Incorrect type " + type);
+        List<EntityView> entityViews = entityViewDao.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId.getId(),
+                customerId.getId(), type, pageLink);
+        return new TextPageData<>(entityViews, pageLink);
+    }
+
+    @Override
     public ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query) {
         ListenableFuture<List<EntityRelation>> relations = relationService.findByQuery(query.toEntitySearchQuery());
         ListenableFuture<List<EntityView>> entityViews = Futures.transformAsync(relations, r -> {
@@ -184,6 +213,15 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
             }
             return Futures.successfulAsList(futures);
         });
+
+        entityViews = Futures.transform(entityViews, new Function<List<EntityView>, List<EntityView>>() {
+            @Nullable
+            @Override
+            public List<EntityView> apply(@Nullable List<EntityView> entityViewList) {
+                return entityViewList == null ? Collections.emptyList() : entityViewList.stream().filter(entityView -> query.getEntityViewTypes().contains(entityView.getType())).collect(Collectors.toList());
+            }
+        });
+
         return entityViews;
     }
 
@@ -216,6 +254,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
                         public void onSuccess(@Nullable List<EntityView> result) {
                             cache.putIfAbsent(tenantIdAndEntityId, result);
                         }
+
                         @Override
                         public void onFailure(Throwable t) {
                             log.error("Error while finding entity views by tenantId and entityId", t);
@@ -243,6 +282,18 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
         tenantEntityViewRemover.removeEntities(tenantId);
     }
 
+    @Override
+    public ListenableFuture<List<EntitySubtype>> findEntityViewTypesByTenantId(TenantId tenantId) {
+        log.trace("Executing findEntityViewTypesByTenantId, tenantId [{}]", tenantId);
+        validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+        ListenableFuture<List<EntitySubtype>> tenantEntityViewTypes = entityViewDao.findTenantEntityViewTypesAsync(tenantId.getId());
+        return Futures.transform(tenantEntityViewTypes,
+                entityViewTypes -> {
+                    entityViewTypes.sort(Comparator.comparing(EntitySubtype::getType));
+                    return entityViewTypes;
+                });
+    }
+
     private ListenableFuture<List<Void>> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection<String> keys) {
         if (keys != null && !keys.isEmpty()) {
             ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys);
@@ -296,6 +347,9 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
 
                 @Override
                 protected void validateDataImpl(EntityView entityView) {
+                    if (StringUtils.isEmpty(entityView.getType())) {
+                        throw new DataValidationException("Entity View type should be specified!");
+                    }
                     if (StringUtils.isEmpty(entityView.getName())) {
                         throw new DataValidationException("Entity view name should be specified!");
                     }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
index a487ede..86ba594 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
@@ -145,18 +145,21 @@ public class ModelConstants {
     /**
      * Cassandra entityView constants.
      */
-    public static final String ENTITY_VIEW_TABLE_FAMILY_NAME = "entity_views";
+    public static final String ENTITY_VIEW_TABLE_FAMILY_NAME = "entity_view";
     public static final String ENTITY_VIEW_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN;
     public static final String ENTITY_VIEW_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
     public static final String ENTITY_VIEW_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
     public static final String ENTITY_VIEW_NAME_PROPERTY = DEVICE_NAME_PROPERTY;
     public static final String ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF = "entity_view_by_tenant_and_customer";
+    public static final String ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_TYPE_CF = "entity_view_by_tenant_and_customer_and_type";
     public static final String ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF = "entity_view_by_tenant_and_entity_id";
     public static final String ENTITY_VIEW_KEYS_PROPERTY = "keys";
+    public static final String ENTITY_VIEW_TYPE_PROPERTY = "type";
     public static final String ENTITY_VIEW_START_TS_PROPERTY = "start_ts";
     public static final String ENTITY_VIEW_END_TS_PROPERTY = "end_ts";
     public static final String ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
-    public static final String ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "entity_view_by_tenant_and_search_text";
+    public static final String ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_CF = "entity_view_by_tenant_and_search_text";
+    public static final String ENTITY_VIEW_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_CF = "entity_view_by_tenant_by_type_and_search_text";
     public static final String ENTITY_VIEW_BY_TENANT_AND_NAME = "entity_view_by_tenant_and_name";
 
     /**
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java
index eb7f4fe..cda4217 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java
@@ -41,6 +41,7 @@ import javax.persistence.Enumerated;
 import java.io.IOException;
 import java.util.UUID;
 
+import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TYPE_PROPERTY;
 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY;
 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME;
 import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
@@ -71,6 +72,10 @@ public class EntityViewEntity implements SearchTextEntity<EntityView> {
     @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY)
     private UUID customerId;
 
+    @PartitionKey(value = 3)
+    @Column(name = DEVICE_TYPE_PROPERTY)
+    private String type;
+
     @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY)
     private UUID entityId;
 
@@ -113,6 +118,7 @@ public class EntityViewEntity implements SearchTextEntity<EntityView> {
         if (entityView.getCustomerId() != null) {
             this.customerId = entityView.getCustomerId().getId();
         }
+        this.type = entityView.getType();
         this.name = entityView.getName();
         try {
             this.keys = mapper.writeValueAsString(entityView.getKeys());
@@ -143,6 +149,7 @@ public class EntityViewEntity implements SearchTextEntity<EntityView> {
         if (customerId != null) {
             entityView.setCustomerId(new CustomerId(customerId));
         }
+        entityView.setType(type);
         entityView.setName(name);
         try {
             entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class));
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java
index 60f6951..6999c0c 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java
@@ -69,6 +69,9 @@ public class EntityViewEntity extends BaseSqlEntity<EntityView> implements Searc
     @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY)
     private String customerId;
 
+    @Column(name = ModelConstants.DEVICE_TYPE_PROPERTY)
+    private String type;
+
     @Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY)
     private String name;
 
@@ -108,6 +111,7 @@ public class EntityViewEntity extends BaseSqlEntity<EntityView> implements Searc
         if (entityView.getCustomerId() != null) {
             this.customerId = toString(entityView.getCustomerId().getId());
         }
+        this.type = entityView.getType();
         this.name = entityView.getName();
         try {
             this.keys = mapper.writeValueAsString(entityView.getKeys());
@@ -144,6 +148,7 @@ public class EntityViewEntity extends BaseSqlEntity<EntityView> implements Searc
         if (customerId != null) {
             entityView.setCustomerId(new CustomerId(toUUID(customerId)));
         }
+        entityView.setType(type);
         entityView.setName(name);
         try {
             entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class));
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java
index 0f5fdf5..efd1bd9 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java
@@ -19,8 +19,6 @@ import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.CrudRepository;
 import org.springframework.data.repository.query.Param;
-import org.thingsboard.server.common.data.EntityView;
-import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.dao.model.sql.EntityViewEntity;
 import org.thingsboard.server.dao.util.SqlDao;
 
@@ -36,21 +34,46 @@ public interface EntityViewRepository extends CrudRepository<EntityViewEntity, S
             "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " +
             "AND e.id > :idOffset ORDER BY e.id")
     List<EntityViewEntity> findByTenantId(@Param("tenantId") String tenantId,
-                                      @Param("textSearch") String textSearch,
-                                      @Param("idOffset") String idOffset,
-                                      Pageable pageable);
+                                          @Param("textSearch") String textSearch,
+                                          @Param("idOffset") String idOffset,
+                                          Pageable pageable);
+
+    @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
+            "AND e.type = :type " +
+            "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " +
+            "AND e.id > :idOffset ORDER BY e.id")
+    List<EntityViewEntity> findByTenantIdAndType(@Param("tenantId") String tenantId,
+                                                 @Param("type") String type,
+                                                 @Param("textSearch") String textSearch,
+                                                 @Param("idOffset") String idOffset,
+                                                 Pageable pageable);
 
     @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
             "AND e.customerId = :customerId " +
             "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
             "AND e.id > :idOffset ORDER BY e.id")
     List<EntityViewEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
-                                                   @Param("customerId") String customerId,
-                                                   @Param("searchText") String searchText,
-                                                   @Param("idOffset") String idOffset,
-                                                   Pageable pageable);
+                                                       @Param("customerId") String customerId,
+                                                       @Param("searchText") String searchText,
+                                                       @Param("idOffset") String idOffset,
+                                                       Pageable pageable);
+
+    @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
+            "AND e.customerId = :customerId " +
+            "AND e.type = :type " +
+            "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
+            "AND e.id > :idOffset ORDER BY e.id")
+    List<EntityViewEntity> findByTenantIdAndCustomerIdAndType(@Param("tenantId") String tenantId,
+                                                              @Param("customerId") String customerId,
+                                                              @Param("type") String type,
+                                                              @Param("searchText") String searchText,
+                                                              @Param("idOffset") String idOffset,
+                                                              Pageable pageable);
 
     EntityViewEntity findByTenantIdAndName(String tenantId, String name);
 
     List<EntityViewEntity> findAllByTenantIdAndEntityId(String tenantId, String entityId);
+
+    @Query("SELECT DISTINCT ev.type FROM EntityViewEntity ev WHERE ev.tenantId = :tenantId")
+    List<String> findTenantEntityViewTypes(@Param("tenantId") String tenantId);
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java
index 912c9d5..1ba9b9e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java
@@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.EntitySubtype;
 import org.thingsboard.server.common.data.EntityType;
 import org.thingsboard.server.common.data.EntityView;
 import org.thingsboard.server.common.data.UUIDConverter;
-import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.page.TextPageLink;
 import org.thingsboard.server.dao.DaoUtil;
@@ -41,7 +40,6 @@ import java.util.Optional;
 import java.util.UUID;
 
 import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
-import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUIDs;
 import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
 
 /**
@@ -76,6 +74,17 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao<EntityViewEntity,
     }
 
     @Override
+    public List<EntityView> findEntityViewsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) {
+        return DaoUtil.convertDataList(
+                entityViewRepository.findByTenantIdAndType(
+                        fromTimeUUID(tenantId),
+                        type,
+                        Objects.toString(pageLink.getTextSearch(), ""),
+                        pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
+                        new PageRequest(0, pageLink.getLimit())));
+    }
+
+    @Override
     public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) {
         return Optional.ofNullable(
                 DaoUtil.getData(entityViewRepository.findByTenantIdAndName(fromTimeUUID(tenantId), name)));
@@ -96,8 +105,37 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao<EntityViewEntity,
     }
 
     @Override
+    public List<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) {
+        return DaoUtil.convertDataList(
+                entityViewRepository.findByTenantIdAndCustomerIdAndType(
+                        fromTimeUUID(tenantId),
+                        fromTimeUUID(customerId),
+                        type,
+                        Objects.toString(pageLink.getTextSearch(), ""),
+                        pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
+                        new PageRequest(0, pageLink.getLimit())
+                ));
+    }
+
+    @Override
     public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) {
         return service.submit(() -> DaoUtil.convertDataList(
                 entityViewRepository.findAllByTenantIdAndEntityId(UUIDConverter.fromTimeUUID(tenantId), UUIDConverter.fromTimeUUID(entityId))));
     }
+
+    @Override
+    public ListenableFuture<List<EntitySubtype>> findTenantEntityViewTypesAsync(UUID tenantId) {
+        return service.submit(() -> convertTenantEntityViewTypesToDto(tenantId, entityViewRepository.findTenantEntityViewTypes(fromTimeUUID(tenantId))));
+    }
+
+    private List<EntitySubtype> convertTenantEntityViewTypesToDto(UUID tenantId, List<String> types) {
+        List<EntitySubtype> list = Collections.emptyList();
+        if (types != null && !types.isEmpty()) {
+            list = new ArrayList<>();
+            for (String type : types) {
+                list.add(new EntitySubtype(new TenantId(tenantId), EntityType.ENTITY_VIEW, type));
+            }
+        }
+        return list;
+    }
 }
diff --git a/dao/src/main/resources/cassandra/schema-entities.cql b/dao/src/main/resources/cassandra/schema-entities.cql
index 7ccd41f..ef27c70 100644
--- a/dao/src/main/resources/cassandra/schema-entities.cql
+++ b/dao/src/main/resources/cassandra/schema-entities.cql
@@ -624,61 +624,90 @@ CREATE TABLE IF NOT EXISTS  thingsboard.rule_node (
     PRIMARY KEY (id)
 );
 
-CREATE TABLE IF NOT EXISTS thingsboard.entity_views (
+CREATE TABLE IF NOT EXISTS thingsboard.entity_view (
     id timeuuid,
     entity_id timeuuid,
     entity_type text,
     tenant_id timeuuid,
     customer_id timeuuid,
     name text,
+    type text,
     keys text,
     start_ts bigint,
     end_ts bigint,
     search_text text,
     additional_info text,
-    PRIMARY KEY (id, entity_id, tenant_id, customer_id)
+    PRIMARY KEY (id, entity_id, tenant_id, customer_id, type)
 );
 
 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS
     SELECT *
-    from thingsboard.entity_views
+    from thingsboard.entity_view
     WHERE tenant_id IS NOT NULL
       AND entity_id IS NOT NULL
       AND customer_id IS NOT NULL
+      AND type IS NOT NULL
       AND name IS NOT NULL
       AND id IS NOT NULL
-    PRIMARY KEY (tenant_id, name, id, customer_id, entity_id)
+    PRIMARY KEY (tenant_id, name, id, customer_id, entity_id, type)
     WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC);
 
 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS
     SELECT *
-    from thingsboard.entity_views
+    from thingsboard.entity_view
     WHERE tenant_id IS NOT NULL
       AND entity_id IS NOT NULL
       AND customer_id IS NOT NULL
+      AND type IS NOT NULL
       AND search_text IS NOT NULL
       AND id IS NOT NULL
-    PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id)
+    PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id, type)
     WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC);
 
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_by_type_and_search_text AS
+    SELECT *
+    from thingsboard.entity_view
+    WHERE tenant_id IS NOT NULL
+      AND entity_id IS NOT NULL
+      AND customer_id IS NOT NULL
+      AND type IS NOT NULL
+      AND search_text IS NOT NULL
+      AND id IS NOT NULL
+    PRIMARY KEY (tenant_id, type, search_text, id, customer_id, entity_id)
+    WITH CLUSTERING ORDER BY (type ASC, search_text ASC, id DESC, customer_id DESC);
+
 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS
     SELECT *
-    from thingsboard.entity_views
+    from thingsboard.entity_view
     WHERE tenant_id IS NOT NULL
       AND customer_id IS NOT NULL
       AND entity_id IS NOT NULL
+      AND type IS NOT NULL
       AND search_text IS NOT NULL
       AND id IS NOT NULL
-    PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id)
+    PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id, type)
     WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC);
 
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer_and_type AS
+    SELECT *
+    from thingsboard.entity_view
+    WHERE tenant_id IS NOT NULL
+      AND customer_id IS NOT NULL
+      AND entity_id IS NOT NULL
+      AND type IS NOT NULL
+      AND search_text IS NOT NULL
+      AND id IS NOT NULL
+    PRIMARY KEY (tenant_id, type, customer_id, search_text, id, entity_id)
+    WITH CLUSTERING ORDER BY (type ASC, customer_id DESC, search_text ASC, id DESC);
+
 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS
     SELECT *
-    from thingsboard.entity_views
+    from thingsboard.entity_view
     WHERE tenant_id IS NOT NULL
       AND customer_id IS NOT NULL
       AND entity_id IS NOT NULL
+      AND type IS NOT NULL
       AND search_text IS NOT NULL
       AND id IS NOT NULL
-    PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id)
+    PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id, type)
     WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);
\ No newline at end of file
diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql
index abd20ab..0b9c853 100644
--- a/dao/src/main/resources/sql/schema-entities.sql
+++ b/dao/src/main/resources/sql/schema-entities.sql
@@ -228,12 +228,13 @@ CREATE TABLE IF NOT EXISTS rule_node (
     search_text varchar(255)
 );
 
-CREATE TABLE IF NOT EXISTS entity_views (
+CREATE TABLE IF NOT EXISTS entity_view (
     id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
     entity_id varchar(31),
     entity_type varchar(255),
     tenant_id varchar(31),
     customer_id varchar(31),
+    type varchar(255),
     name varchar(255),
     keys varchar(255),
     start_ts bigint,
diff --git a/dao/src/test/resources/sql/drop-all-tables.sql b/dao/src/test/resources/sql/drop-all-tables.sql
index b1fb72c..1bdc1a7 100644
--- a/dao/src/test/resources/sql/drop-all-tables.sql
+++ b/dao/src/test/resources/sql/drop-all-tables.sql
@@ -19,4 +19,4 @@ DROP TABLE IF EXISTS widget_type;
 DROP TABLE IF EXISTS widgets_bundle;
 DROP TABLE IF EXISTS rule_node;
 DROP TABLE IF EXISTS rule_chain;
-DROP TABLE IF EXISTS entity_views;
+DROP TABLE IF EXISTS entity_view;
diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js
index ce4dc42..205abc9 100644
--- a/ui/src/app/api/entity.service.js
+++ b/ui/src/app/api/entity.service.js
@@ -533,6 +533,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                     }
                 );
                 break;
+            case types.aliasFilterType.entityViewType.value:
+                getEntitiesByNameFilter(types.entityType.entityView, filter.entityViewNameFilter, maxItems, {ignoreLoading: true}, filter.entityViewType).then(
+                    function success(entities) {
+                        if (entities && entities.length || !failOnEmpty) {
+                            result.entities = entitiesToEntitiesInfo(entities);
+                            deferred.resolve(result);
+                        } else {
+                            deferred.reject();
+                        }
+                    },
+                    function fail() {
+                        deferred.reject();
+                    }
+                );
+                break;
             case types.aliasFilterType.relationsQuery.value:
                 result.stateEntity = filter.rootStateEntity;
                 var rootEntityType;
@@ -578,6 +593,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 break;
             case types.aliasFilterType.assetSearchQuery.value:
             case types.aliasFilterType.deviceSearchQuery.value:
+            case types.aliasFilterType.entityViewSearchQuery.value:
                 result.stateEntity = filter.rootStateEntity;
                 if (result.stateEntity && stateEntityId) {
                     rootEntityType = stateEntityId.entityType;
@@ -604,6 +620,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                     } else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) {
                         searchQuery.deviceTypes = filter.deviceTypes;
                         findByQueryPromise = deviceService.findByQuery(searchQuery, false, {ignoreLoading: true});
+                    } else if (filter.type == types.aliasFilterType.entityViewSearchQuery.value) {
+                        searchQuery.entityViewTypes = filter.entityViewTypes;
+                        findByQueryPromise = entityViewService.findByQuery(searchQuery, false, {ignoreLoading: true});
                     }
                     findByQueryPromise.then(
                         function success(entities) {
@@ -646,6 +665,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                     return entityTypes.indexOf(types.entityType.asset)  > -1 ? true : false;
                 case types.aliasFilterType.deviceType.value:
                     return entityTypes.indexOf(types.entityType.device)  > -1 ? true : false;
+                case types.aliasFilterType.entityViewType.value:
+                    return entityTypes.indexOf(types.entityType.entityView)  > -1 ? true : false;
                 case types.aliasFilterType.relationsQuery.value:
                     if (filter.filters && filter.filters.length) {
                         var match = false;
@@ -671,6 +692,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                     return entityTypes.indexOf(types.entityType.asset)  > -1 ? true : false;
                 case types.aliasFilterType.deviceSearchQuery.value:
                     return entityTypes.indexOf(types.entityType.device)  > -1 ? true : false;
+                case types.aliasFilterType.entityViewSearchQuery.value:
+                    return entityTypes.indexOf(types.entityType.entityView)  > -1 ? true : false;
             }
         }
         return false;
@@ -690,12 +713,16 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 return entityType === types.entityType.asset;
             case types.aliasFilterType.deviceType.value:
                 return entityType === types.entityType.device;
+            case types.aliasFilterType.entityViewType.value:
+                return entityType === types.entityType.entityView;
             case types.aliasFilterType.relationsQuery.value:
                 return true;
             case types.aliasFilterType.assetSearchQuery.value:
                 return entityType === types.entityType.asset;
             case types.aliasFilterType.deviceSearchQuery.value:
                 return entityType === types.entityType.device;
+            case types.aliasFilterType.entityViewSearchQuery.value:
+                return entityType === types.entityType.entityView;
         }
         return false;
     }
@@ -1046,6 +1073,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
             return assetService.deleteAsset(entityId.id);
         } else if (entityId.entityType == types.entityType.device) {
             return deviceService.deleteDevice(entityId.id);
+        } else if (entityId.entityType == types.entityType.entityView) {
+            return entityViewService.deleteEntityView(entityId.id);
         }
     }
 
@@ -1151,6 +1180,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
             return assetService.saveAsset(entity);
         } else if (entityType == types.entityType.device) {
             return deviceService.saveDevice(entity);
+        } else if (entityType == types.entityType.entityView) {
+            return entityViewService.saveEntityView(entity);
         }
     }
 
@@ -1279,6 +1310,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
             searchQuery.assetTypes = entitySubTypes;
         } else if (entityType == types.entityType.device) {
             searchQuery.deviceTypes = entitySubTypes;
+        } else if (entityType == types.entityType.entityView) {
+            searchQuery.entityViewTypes = entitySubTypes;
         } else {
             return null; //Not supported
         }
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index 15da584..506ef1d 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -253,6 +253,10 @@ export default angular.module('thingsboard.types', [])
                     value: 'deviceType',
                     name: 'alias.filter-type-device-type'
                 },
+                entityViewType: {
+                    value: 'entityViewType',
+                    name: 'alias.filter-type-entity-view-type'
+                },
                 relationsQuery: {
                     value: 'relationsQuery',
                     name: 'alias.filter-type-relations-query'
@@ -264,6 +268,10 @@ export default angular.module('thingsboard.types', [])
                 deviceSearchQuery: {
                     value: 'deviceSearchQuery',
                     name: 'alias.filter-type-device-search-query'
+                },
+                entityViewSearchQuery: {
+                    value: 'entityViewSearchQuery',
+                    name: 'alias.filter-type-entity-view-search-query'
                 }
             },
             position: {
diff --git a/ui/src/app/entity/entity-filter.directive.js b/ui/src/app/entity/entity-filter.directive.js
index 0c8f646..38641fb 100644
--- a/ui/src/app/entity/entity-filter.directive.js
+++ b/ui/src/app/entity/entity-filter.directive.js
@@ -69,9 +69,14 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
                     filter.deviceType = null;
                     filter.deviceNameFilter = '';
                     break;
+                case types.aliasFilterType.entityViewType.value:
+                    filter.entityViewType = null;
+                    filter.entityViewNameFilter = '';
+                    break;
                 case types.aliasFilterType.relationsQuery.value:
                 case types.aliasFilterType.assetSearchQuery.value:
                 case types.aliasFilterType.deviceSearchQuery.value:
+                case types.aliasFilterType.entityViewSearchQuery.value:
                     filter.rootStateEntity = false;
                     filter.stateEntityParamName = null;
                     filter.defaultStateEntity = null;
@@ -86,6 +91,9 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
                     } else if (filter.type === types.aliasFilterType.deviceSearchQuery.value) {
                         filter.relationType = null;
                         filter.deviceTypes = [];
+                    } else if (filter.type === types.aliasFilterType.entityViewSearchQuery.value) {
+                        filter.relationType = null;
+                        filter.entityViewTypes = [];
                     }
                     break;
             }
diff --git a/ui/src/app/entity/entity-filter.tpl.html b/ui/src/app/entity/entity-filter.tpl.html
index f9aac3c..95193f2 100644
--- a/ui/src/app/entity/entity-filter.tpl.html
+++ b/ui/src/app/entity/entity-filter.tpl.html
@@ -112,6 +112,20 @@
                    aria-label="{{ 'device.name-starts-with' | translate }}">
         </md-input-container>
     </section>
+    <section layout="column" ng-if="filter.type == types.aliasFilterType.entityViewType.value" id="entityViewTypeFilter">
+        <tb-entity-subtype-autocomplete
+                tb-required="true"
+                the-form="theForm"
+                ng-model="filter.entityViewType"
+                entity-type="types.entityType.entityView">
+        </tb-entity-subtype-autocomplete>
+        <md-input-container class="md-block">
+            <label translate>entity-view.name-starts-with</label>
+            <input name="entityViewNameFilter"
+                   ng-model="filter.entityViewNameFilter"
+                   aria-label="{{ 'entity-view.name-starts-with' | translate }}">
+        </md-input-container>
+    </section>
     <section layout="column" ng-if="filter.type == types.aliasFilterType.relationsQuery.value" id="relationsQueryFilter">
         <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
         <section class="tb-root-state-entity-switch" layout="row" layout-align="start center" style="padding-left: 0px;">
@@ -311,4 +325,73 @@
                 ng-model="filter.deviceTypes">
         </tb-entity-subtype-list>
     </section>
+    <section layout="column" ng-if="filter.type == types.aliasFilterType.entityViewSearchQuery.value" id="entityViewSearchQueryFilter">
+        <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
+        <section class="tb-root-state-entity-switch" layout="row" layout-align="start center" style="padding-left: 0px;">
+            <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
+                       aria-label="{{ 'alias.root-state-entity' | translate }}">
+            </md-switch>
+            <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
+        </section>
+        <div flex layout="row" ng-if="!filter.rootStateEntity">
+            <tb-entity-select flex
+                              the-form="theForm"
+                              tb-required="!filter.rootStateEntity"
+                              ng-disabled="filter.rootStateEntity"
+                              use-alias-entity-types="true"
+                              ng-model="filter.rootEntity">
+            </tb-entity-select>
+        </div>
+        <div flex layout="row" ng-if="filter.rootStateEntity">
+            <md-input-container class="md-block" style="margin-top: 32px;">
+                <label translate>alias.state-entity-parameter-name</label>
+                <input name="stateEntityParamName"
+                       placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
+                       ng-model="filter.stateEntityParamName"
+                       aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
+            </md-input-container>
+            <div flex layout="column">
+                <label class="tb-small">{{ 'alias.default-state-entity' | translate }}</label>
+                <tb-entity-select flex
+                                  the-form="theForm"
+                                  tb-required="false"
+                                  use-alias-entity-types="true"
+                                  ng-model="filter.defaultStateEntity">
+                </tb-entity-select>
+            </div>
+        </div>
+        <div flex layout="row">
+            <md-input-container class="md-block" style="min-width: 100px;">
+                <label translate>relation.direction</label>
+                <md-select required ng-model="filter.direction">
+                    <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
+                        {{ ('relation.search-direction.' + direction) | translate}}
+                    </md-option>
+                </md-select>
+            </md-input-container>
+            <md-input-container flex class="md-block">
+                <label translate>alias.max-relation-level</label>
+                <input name="maxRelationLevel"
+                       type="number"
+                       min="1"
+                       step="1"
+                       placeholder="{{ 'alias.unlimited-level' | translate }}"
+                       ng-model="filter.maxLevel"
+                       aria-label="{{ 'alias.max-relation-level' | translate }}">
+            </md-input-container>
+        </div>
+        <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div>
+        <tb-relation-type-autocomplete flex
+                                       hide-label
+                                       the-form="theForm"
+                                       ng-model="filter.relationType"
+                                       tb-required="false">
+        </tb-relation-type-autocomplete>
+        <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>entity-view.entity-view-types</div>
+        <tb-entity-subtype-list
+                tb-required="true"
+                entity-type="types.entityType.entityView"
+                ng-model="filter.entityViewTypes">
+        </tb-entity-subtype-list>
+    </section>
 </div>
diff --git a/ui/src/app/entity/entity-filter-view.directive.js b/ui/src/app/entity/entity-filter-view.directive.js
index 8bd3422..5b34107 100644
--- a/ui/src/app/entity/entity-filter-view.directive.js
+++ b/ui/src/app/entity/entity-filter-view.directive.js
@@ -77,6 +77,15 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q, 
                             scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType});
                         }
                         break;
+                    case types.aliasFilterType.entityViewType.value:
+                        var entityViewType = scope.filter.entityViewType;
+                        prefix = scope.filter.entityViewNameFilter;
+                        if (prefix && prefix.length) {
+                            scope.filterDisplayValue = $translate.instant('alias.filter-type-entity-view-type-and-name-description', {entityViewType: entityViewType, prefix: prefix});
+                        } else {
+                            scope.filterDisplayValue = $translate.instant('alias.filter-type-entity-view-type-description', {entityViewType: entityViewType});
+                        }
+                        break;
                     case types.aliasFilterType.relationsQuery.value:
                         var rootEntityText;
                         var directionText;
@@ -134,6 +143,7 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q, 
                         break;
                     case types.aliasFilterType.assetSearchQuery.value:
                     case types.aliasFilterType.deviceSearchQuery.value:
+                    case types.aliasFilterType.entityViewSearchQuery.value:
                         allEntitiesText = $translate.instant('alias.all-entities');
                         anyRelationText = $translate.instant('alias.any-relation');
                         if (scope.filter.rootStateEntity) {
@@ -165,7 +175,7 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q, 
                             scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-search-query-description',
                                 translationValues
                             );
-                        } else {
+                        } else if (scope.filter.type == types.aliasFilterType.deviceSearchQuery.value) {
                             var deviceTypesQuoted = [];
                             scope.filter.deviceTypes.forEach(function(deviceType) {
                                 deviceTypesQuoted.push("'"+deviceType+"'");
@@ -175,6 +185,16 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q, 
                             scope.filterDisplayValue = $translate.instant('alias.filter-type-device-search-query-description',
                                 translationValues
                             );
+                        } else if (scope.filter.type == types.aliasFilterType.entityViewSearchQuery.value) {
+                            var entityViewTypesQuoted = [];
+                            scope.filter.entityViewTypes.forEach(function(entityViewType) {
+                                entityViewTypesQuoted.push("'"+entityViewType+"'");
+                            });
+                            var entityViewTypesText = entityViewTypesQuoted.join(', ');
+                            translationValues.entityViewTypes = entityViewTypesText;
+                            scope.filterDisplayValue = $translate.instant('alias.filter-type-entity-view-search-query-description',
+                                translationValues
+                            );
                         }
                         break;
                     default:
diff --git a/ui/src/app/entity/entity-subtype-autocomplete.directive.js b/ui/src/app/entity/entity-subtype-autocomplete.directive.js
index 5812715..4c79f9a 100644
--- a/ui/src/app/entity/entity-subtype-autocomplete.directive.js
+++ b/ui/src/app/entity/entity-subtype-autocomplete.directive.js
@@ -22,7 +22,7 @@ import entitySubtypeAutocompleteTemplate from './entity-subtype-autocomplete.tpl
 /* eslint-enable import/no-unresolved, import/default */
 
 /*@ngInject*/
-export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, $filter, assetService, deviceService, types) {
+export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, $filter, assetService, deviceService, entityViewService, types) {
 
     var linker = function (scope, element, attrs, ngModelCtrl) {
         var template = $templateCache.get(entitySubtypeAutocompleteTemplate);
@@ -96,6 +96,8 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, 
                     entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true});
                 } else if (scope.entityType == types.entityType.device) {
                     entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
+                } else if (scope.entityType == types.entityType.entityView) {
+                    entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
                 }
                 if (entitySubtypesPromise) {
                     entitySubtypesPromise.then(
@@ -134,6 +136,13 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, 
                 scope.$on('deviceSaved', function() {
                     scope.entitySubtypes = null;
                 });
+            } else if (scope.entityType == types.entityType.entityView) {
+                scope.selectEntitySubtypeText = 'entity-view.select-entity-view-type';
+                scope.entitySubtypeText = 'entity-view.entity-view-type';
+                scope.entitySubtypeRequiredText = 'entity-view.entity-view-type-required';
+                scope.$on('entityViewSaved', function() {
+                    scope.entitySubtypes = null;
+                });
             }
         }
 
diff --git a/ui/src/app/entity/entity-subtype-list.directive.js b/ui/src/app/entity/entity-subtype-list.directive.js
index d74a7b0..2da948d 100644
--- a/ui/src/app/entity/entity-subtype-list.directive.js
+++ b/ui/src/app/entity/entity-subtype-list.directive.js
@@ -22,7 +22,7 @@ import entitySubtypeListTemplate from './entity-subtype-list.tpl.html';
 import './entity-subtype-list.scss';
 
 /*@ngInject*/
-export default function EntitySubtypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, assetService, deviceService) {
+export default function EntitySubtypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, assetService, deviceService, entityViewService) {
 
     var linker = function (scope, element, attrs, ngModelCtrl) {
 
@@ -97,6 +97,8 @@ export default function EntitySubtypeListDirective($compile, $templateCache, $q,
                     entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true});
                 } else if (scope.entityType == types.entityType.device) {
                     entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
+                } else if (scope.entityType == types.entityType.entityView) {
+                    entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
                 }
                 if (entitySubtypesPromise) {
                     entitySubtypesPromise.then(
diff --git a/ui/src/app/entity/entity-subtype-select.directive.js b/ui/src/app/entity/entity-subtype-select.directive.js
index b86e944..d6163da 100644
--- a/ui/src/app/entity/entity-subtype-select.directive.js
+++ b/ui/src/app/entity/entity-subtype-select.directive.js
@@ -22,7 +22,7 @@ import entitySubtypeSelectTemplate from './entity-subtype-select.tpl.html';
 /* eslint-enable import/no-unresolved, import/default */
 
 /*@ngInject*/
-export default function EntitySubtypeSelect($compile, $templateCache, $translate, assetService, deviceService, types) {
+export default function EntitySubtypeSelect($compile, $templateCache, $translate, assetService, deviceService, entityViewService, types) {
 
     var linker = function (scope, element, attrs, ngModelCtrl) {
         var template = $templateCache.get(entitySubtypeSelectTemplate);
@@ -75,6 +75,8 @@ export default function EntitySubtypeSelect($compile, $templateCache, $translate
                 entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true});
             } else if (scope.entityType == types.entityType.device) {
                 entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
+            } else if (scope.entityType == types.entityType.entityView) {
+                entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
             }
             if (entitySubtypesPromise) {
                 entitySubtypesPromise.then(
diff --git a/ui/src/app/entity-view/entity-view-fieldset.tpl.html b/ui/src/app/entity-view/entity-view-fieldset.tpl.html
index 3894eb9..66dc116 100644
--- a/ui/src/app/entity-view/entity-view-fieldset.tpl.html
+++ b/ui/src/app/entity-view/entity-view-fieldset.tpl.html
@@ -52,12 +52,22 @@
 	      		<div translate ng-message="required">entity-view.name-required</div>
 	    	</div>				
 		</md-input-container>
-        <tb-entity-select flex ng-disabled="!isEdit"
-                          the-form="theForm"
-                          tb-required="true"
-                          allowed-entity-types="allowedEntityTypes"
-                          ng-model="entityView.entityId">
-        </tb-entity-select>
+        <tb-entity-subtype-autocomplete
+                ng-disabled="$root.loading || !isEdit"
+                tb-required="true"
+                the-form="theForm"
+                ng-model="entityView.type"
+                entity-type="types.entityType.entityView">
+        </tb-entity-subtype-autocomplete>
+        <section layout="column">
+            <label translate class="tb-title no-padding">entity-view.related-entity</label>
+            <tb-entity-select flex ng-disabled="!isEdit"
+                              the-form="theForm"
+                              tb-required="true"
+                              allowed-entity-types="allowedEntityTypes"
+                              ng-model="entityView.entityId">
+            </tb-entity-select>
+        </section>
         <md-input-container class="md-block">
             <label translate>entity-view.description</label>
             <textarea ng-model="entityView.additionalInfo.description" rows="2"></textarea>
diff --git a/ui/src/app/help/help-links.constant.js b/ui/src/app/help/help-links.constant.js
index 458c118..9e6bb52 100644
--- a/ui/src/app/help/help-links.constant.js
+++ b/ui/src/app/help/help-links.constant.js
@@ -96,6 +96,7 @@ export default angular.module('thingsboard.help', [])
                 customers: helpBaseUrl + "/docs/user-guide/ui/customers",
                 assets: helpBaseUrl + "/docs/user-guide/ui/assets",
                 devices: helpBaseUrl + "/docs/user-guide/ui/devices",
+                entityViews: helpBaseUrl + "/docs/user-guide/ui/entity-views",
                 dashboards: helpBaseUrl + "/docs/user-guide/ui/dashboards",
                 users: helpBaseUrl + "/docs/user-guide/ui/users",
                 widgetsBundles: helpBaseUrl + "/docs/user-guide/ui/widget-library#bundles",
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json
index 481d677..6c6ea43 100644
--- a/ui/src/app/locale/locale.constant-en_US.json
+++ b/ui/src/app/locale/locale.constant-en_US.json
@@ -158,12 +158,17 @@
         "filter-type-device-type": "Device type",
         "filter-type-device-type-description": "Devices of type '{{deviceType}}'",
         "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'",
+        "filter-type-entity-view-type": "Entity View type",
+        "filter-type-entity-view-type-description": "Entity Views of type '{{entityView}}'",
+        "filter-type-entity-view-type-and-name-description": "Entity Views of type '{{entityView}}' and with name starting with '{{prefix}}'",
         "filter-type-relations-query": "Relations query",
         "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
         "filter-type-asset-search-query": "Asset search query",
         "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
         "filter-type-device-search-query": "Device search query",
         "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
+        "filter-type-entity-view-search-query": "Entity view search query",
+        "filter-type-entity-view-search-query-description": "Entity views with types {{entityViewTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
         "entity-filter": "Entity filter",
         "resolve-multiple": "Resolve as multiple entities",
         "filter-type": "Filter type",
@@ -839,7 +844,8 @@
         "client-attributes": "Client attributes",
         "shared-attributes": "Shared attributes",
         "server-attributes": "Server attributes",
-        "latest-timeseries": "Latest timeseries"
+        "latest-timeseries": "Latest timeseries",
+        "related-entity": "Related entity"
     },
     "event": {
         "event-type": "Event type",