Commit 94cb347899bab68fde840a9841853b0e7d1b2b55

Authored by Volodymyr Babak
1 parent 6b4a2ff1

Added entity view type

Showing 32 changed files with 727 additions and 34 deletions
  1 +--
  2 +-- Copyright © 2016-2018 The Thingsboard Authors
  3 +--
  4 +-- Licensed under the Apache License, Version 2.0 (the "License");
  5 +-- you may not use this file except in compliance with the License.
  6 +-- You may obtain a copy of the License at
  7 +--
  8 +-- http://www.apache.org/licenses/LICENSE-2.0
  9 +--
  10 +-- Unless required by applicable law or agreed to in writing, software
  11 +-- distributed under the License is distributed on an "AS IS" BASIS,
  12 +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +-- See the License for the specific language governing permissions and
  14 +-- limitations under the License.
  15 +--
  16 +
  17 +DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_name;
  18 +DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_search_text;
  19 +DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer;
  20 +DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id;
  21 +
  22 +DROP TABLE IF EXISTS thingsboard.entity_views;
  23 +
  24 +CREATE TABLE IF NOT EXISTS thingsboard.entity_view (
  25 + id timeuuid,
  26 + entity_id timeuuid,
  27 + entity_type text,
  28 + tenant_id timeuuid,
  29 + customer_id timeuuid,
  30 + name text,
  31 + type text,
  32 + keys text,
  33 + start_ts bigint,
  34 + end_ts bigint,
  35 + search_text text,
  36 + additional_info text,
  37 + PRIMARY KEY (id, entity_id, tenant_id, customer_id, type)
  38 +);
  39 +
  40 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS
  41 + SELECT *
  42 + from thingsboard.entity_view
  43 + WHERE tenant_id IS NOT NULL
  44 + AND entity_id IS NOT NULL
  45 + AND customer_id IS NOT NULL
  46 + AND type IS NOT NULL
  47 + AND name IS NOT NULL
  48 + AND id IS NOT NULL
  49 + PRIMARY KEY (tenant_id, name, id, customer_id, entity_id, type)
  50 + WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC);
  51 +
  52 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS
  53 + SELECT *
  54 + from thingsboard.entity_view
  55 + WHERE tenant_id IS NOT NULL
  56 + AND entity_id IS NOT NULL
  57 + AND customer_id IS NOT NULL
  58 + AND type IS NOT NULL
  59 + AND search_text IS NOT NULL
  60 + AND id IS NOT NULL
  61 + PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id, type)
  62 + WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC);
  63 +
  64 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_by_type_and_search_text AS
  65 + SELECT *
  66 + from thingsboard.entity_view
  67 + WHERE tenant_id IS NOT NULL
  68 + AND entity_id IS NOT NULL
  69 + AND customer_id IS NOT NULL
  70 + AND type IS NOT NULL
  71 + AND search_text IS NOT NULL
  72 + AND id IS NOT NULL
  73 + PRIMARY KEY (tenant_id, type, search_text, id, customer_id, entity_id)
  74 + WITH CLUSTERING ORDER BY (type ASC, search_text ASC, id DESC, customer_id DESC);
  75 +
  76 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS
  77 + SELECT *
  78 + from thingsboard.entity_view
  79 + WHERE tenant_id IS NOT NULL
  80 + AND customer_id IS NOT NULL
  81 + AND entity_id IS NOT NULL
  82 + AND type IS NOT NULL
  83 + AND search_text IS NOT NULL
  84 + AND id IS NOT NULL
  85 + PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id, type)
  86 + WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC);
  87 +
  88 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer_and_type AS
  89 + SELECT *
  90 + from thingsboard.entity_view
  91 + WHERE tenant_id IS NOT NULL
  92 + AND customer_id IS NOT NULL
  93 + AND entity_id IS NOT NULL
  94 + AND type IS NOT NULL
  95 + AND search_text IS NOT NULL
  96 + AND id IS NOT NULL
  97 + PRIMARY KEY (tenant_id, type, customer_id, search_text, id, entity_id)
  98 + WITH CLUSTERING ORDER BY (type ASC, customer_id DESC, search_text ASC, id DESC);
  99 +
  100 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS
  101 + SELECT *
  102 + from thingsboard.entity_view
  103 + WHERE tenant_id IS NOT NULL
  104 + AND customer_id IS NOT NULL
  105 + AND entity_id IS NOT NULL
  106 + AND type IS NOT NULL
  107 + AND search_text IS NOT NULL
  108 + AND id IS NOT NULL
  109 + PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id, type)
  110 + WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);
\ No newline at end of file
... ...
  1 +--
  2 +-- Copyright © 2016-2018 The Thingsboard Authors
  3 +--
  4 +-- Licensed under the Apache License, Version 2.0 (the "License");
  5 +-- you may not use this file except in compliance with the License.
  6 +-- You may obtain a copy of the License at
  7 +--
  8 +-- http://www.apache.org/licenses/LICENSE-2.0
  9 +--
  10 +-- Unless required by applicable law or agreed to in writing, software
  11 +-- distributed under the License is distributed on an "AS IS" BASIS,
  12 +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +-- See the License for the specific language governing permissions and
  14 +-- limitations under the License.
  15 +--
  16 +
  17 +DROP TABLE IF EXISTS entity_views;
  18 +
  19 +CREATE TABLE IF NOT EXISTS entity_view (
  20 + id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
  21 + entity_id varchar(31),
  22 + entity_type varchar(255),
  23 + tenant_id varchar(31),
  24 + customer_id varchar(31),
  25 + type varchar(255),
  26 + name varchar(255),
  27 + keys varchar(255),
  28 + start_ts bigint,
  29 + end_ts bigint,
  30 + search_text varchar(255),
  31 + additional_info varchar
  32 +);
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 19 import org.springframework.http.HttpStatus;
19 20 import org.springframework.security.access.prepost.PreAuthorize;
20 21 import org.springframework.web.bind.annotation.PathVariable;
... ... @@ -26,6 +27,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
26 27 import org.springframework.web.bind.annotation.ResponseStatus;
27 28 import org.springframework.web.bind.annotation.RestController;
28 29 import org.thingsboard.server.common.data.Customer;
  30 +import org.thingsboard.server.common.data.EntitySubtype;
29 31 import org.thingsboard.server.common.data.EntityType;
30 32 import org.thingsboard.server.common.data.EntityView;
31 33 import org.thingsboard.server.common.data.audit.ActionType;
... ... @@ -38,6 +40,7 @@ import org.thingsboard.server.common.data.page.TextPageData;
38 40 import org.thingsboard.server.common.data.page.TextPageLink;
39 41 import org.thingsboard.server.dao.exception.IncorrectParameterException;
40 42 import org.thingsboard.server.dao.model.ModelConstants;
  43 +import org.thingsboard.server.service.security.model.SecurityUser;
41 44
42 45 import java.util.List;
43 46 import java.util.stream.Collectors;
... ... @@ -161,6 +164,7 @@ public class EntityViewController extends BaseController {
161 164 public TextPageData<EntityView> getCustomerEntityViews(
162 165 @PathVariable("customerId") String strCustomerId,
163 166 @RequestParam int limit,
  167 + @RequestParam(required = false) String type,
164 168 @RequestParam(required = false) String textSearch,
165 169 @RequestParam(required = false) String idOffset,
166 170 @RequestParam(required = false) String textOffset) throws ThingsboardException {
... ... @@ -170,7 +174,11 @@ public class EntityViewController extends BaseController {
170 174 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
171 175 checkCustomerId(customerId);
172 176 TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
173   - return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
  177 + if (type != null && type.trim().length() > 0) {
  178 + return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId, customerId, pageLink, type));
  179 + } else {
  180 + return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
  181 + }
174 182 } catch (Exception e) {
175 183 throw handleException(e);
176 184 }
... ... @@ -181,13 +189,19 @@ public class EntityViewController extends BaseController {
181 189 @ResponseBody
182 190 public TextPageData<EntityView> getTenantEntityViews(
183 191 @RequestParam int limit,
  192 + @RequestParam(required = false) String type,
184 193 @RequestParam(required = false) String textSearch,
185 194 @RequestParam(required = false) String idOffset,
186 195 @RequestParam(required = false) String textOffset) throws ThingsboardException {
187 196 try {
188 197 TenantId tenantId = getCurrentUser().getTenantId();
189 198 TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
190   - return checkNotNull(entityViewService.findEntityViewByTenantId(tenantId, pageLink));
  199 +
  200 + if (type != null && type.trim().length() > 0) {
  201 + return checkNotNull(entityViewService.findEntityViewByTenantIdAndType(tenantId, pageLink, type));
  202 + } else {
  203 + return checkNotNull(entityViewService.findEntityViewByTenantId(tenantId, pageLink));
  204 + }
191 205 } catch (Exception e) {
192 206 throw handleException(e);
193 207 }
... ... @@ -199,6 +213,7 @@ public class EntityViewController extends BaseController {
199 213 public List<EntityView> findByQuery(@RequestBody EntityViewSearchQuery query) throws ThingsboardException {
200 214 checkNotNull(query);
201 215 checkNotNull(query.getParameters());
  216 + checkNotNull(query.getEntityViewTypes());
202 217 checkEntityId(query.getParameters().getEntityId());
203 218 try {
204 219 List<EntityView> entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(query).get());
... ... @@ -215,4 +230,18 @@ public class EntityViewController extends BaseController {
215 230 throw handleException(e);
216 231 }
217 232 }
  233 +
  234 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  235 + @RequestMapping(value = "/entityView/types", method = RequestMethod.GET)
  236 + @ResponseBody
  237 + public List<EntitySubtype> getEntityViewTypes() throws ThingsboardException {
  238 + try {
  239 + SecurityUser user = getCurrentUser();
  240 + TenantId tenantId = user.getTenantId();
  241 + ListenableFuture<List<EntitySubtype>> entityViewTypes = entityViewService.findEntityViewTypesByTenantId(tenantId);
  242 + return checkNotNull(entityViewTypes.get());
  243 + } catch (Exception e) {
  244 + throw handleException(e);
  245 + }
  246 + }
218 247 }
... ...
... ... @@ -39,10 +39,19 @@ import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATIO
39 39 import static org.thingsboard.server.service.install.DatabaseHelper.CUSTOMER_ID;
40 40 import static org.thingsboard.server.service.install.DatabaseHelper.DASHBOARD;
41 41 import static org.thingsboard.server.service.install.DatabaseHelper.DEVICE;
  42 +import static org.thingsboard.server.service.install.DatabaseHelper.END_TS;
  43 +import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_ID;
  44 +import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_TYPE;
  45 +import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEW;
  46 +import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEWS;
42 47 import static org.thingsboard.server.service.install.DatabaseHelper.ID;
  48 +import static org.thingsboard.server.service.install.DatabaseHelper.KEYS;
  49 +import static org.thingsboard.server.service.install.DatabaseHelper.NAME;
43 50 import static org.thingsboard.server.service.install.DatabaseHelper.SEARCH_TEXT;
  51 +import static org.thingsboard.server.service.install.DatabaseHelper.START_TS;
44 52 import static org.thingsboard.server.service.install.DatabaseHelper.TENANT_ID;
45 53 import static org.thingsboard.server.service.install.DatabaseHelper.TITLE;
  54 +import static org.thingsboard.server.service.install.DatabaseHelper.TYPE;
46 55
47 56 @Service
48 57 @NoSqlDao
... ... @@ -213,6 +222,36 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
213 222
214 223 break;
215 224
  225 + case "2.1.1":
  226 +
  227 + log.info("Upgrading Cassandara DataBase from version {} to 2.1.1 ...", fromVersion);
  228 +
  229 + cluster.getSession();
  230 +
  231 + ks = cluster.getCluster().getMetadata().getKeyspace(cluster.getKeyspaceName());
  232 +
  233 + log.info("Dumping entity views ...");
  234 + Path entityViewsDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), ENTITY_VIEWS,
  235 + new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, NAME, TYPE, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO},
  236 + new String[]{"", "", "", "", "", "", "default", "", "0", "0", "", ""},
  237 + "tb-entity-views");
  238 + log.info("Entity views dumped.");
  239 +
  240 + log.info("Updating schema ...");
  241 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.2", SCHEMA_UPDATE_CQL);
  242 + loadCql(schemaUpdateFile);
  243 + log.info("Schema updated.");
  244 +
  245 + log.info("Restoring entity views ...");
  246 + if (entityViewsDump != null) {
  247 + CassandraDbHelper.loadCf(ks, cluster.getSession(), ENTITY_VIEW,
  248 + new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, NAME, TYPE, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO}, entityViewsDump);
  249 + Files.deleteIfExists(entityViewsDump);
  250 + }
  251 + log.info("Entity views restored.");
  252 +
  253 + break;
  254 +
216 255 default:
217 256 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
218 257 }
... ...
... ... @@ -45,14 +45,23 @@ public class DatabaseHelper {
45 45 public static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
46 46
47 47 public static final String DEVICE = "device";
  48 + public static final String ENTITY_ID = "entity_id";
48 49 public static final String TENANT_ID = "tenant_id";
  50 + public static final String ENTITY_TYPE = "entity_type";
49 51 public static final String CUSTOMER_ID = "customer_id";
50 52 public static final String SEARCH_TEXT = "search_text";
51 53 public static final String ADDITIONAL_INFO = "additional_info";
52 54 public static final String ASSET = "asset";
53 55 public static final String DASHBOARD = "dashboard";
  56 + public static final String ENTITY_VIEWS = "entity_views";
  57 + public static final String ENTITY_VIEW = "entity_view";
54 58 public static final String ID = "id";
55 59 public static final String TITLE = "title";
  60 + public static final String TYPE = "type";
  61 + public static final String NAME = "name";
  62 + public static final String KEYS = "keys";
  63 + public static final String START_TS = "start_ts";
  64 + public static final String END_TS = "end_ts";
56 65 public static final String ASSIGNED_CUSTOMERS = "assigned_customers";
57 66 public static final String CONFIGURATION = "configuration";
58 67
... ...
... ... @@ -31,14 +31,24 @@ import java.nio.file.Paths;
31 31 import java.sql.Connection;
32 32 import java.sql.DriverManager;
33 33
  34 +import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO;
34 35 import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS;
35 36 import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION;
36 37 import static org.thingsboard.server.service.install.DatabaseHelper.CUSTOMER_ID;
37 38 import static org.thingsboard.server.service.install.DatabaseHelper.DASHBOARD;
  39 +import static org.thingsboard.server.service.install.DatabaseHelper.END_TS;
  40 +import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_ID;
  41 +import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_TYPE;
  42 +import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEW;
  43 +import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEWS;
38 44 import static org.thingsboard.server.service.install.DatabaseHelper.ID;
  45 +import static org.thingsboard.server.service.install.DatabaseHelper.KEYS;
  46 +import static org.thingsboard.server.service.install.DatabaseHelper.NAME;
39 47 import static org.thingsboard.server.service.install.DatabaseHelper.SEARCH_TEXT;
  48 +import static org.thingsboard.server.service.install.DatabaseHelper.START_TS;
40 49 import static org.thingsboard.server.service.install.DatabaseHelper.TENANT_ID;
41 50 import static org.thingsboard.server.service.install.DatabaseHelper.TITLE;
  51 +import static org.thingsboard.server.service.install.DatabaseHelper.TYPE;
42 52
43 53 @Service
44 54 @Profile("install")
... ... @@ -115,6 +125,30 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
115 125 log.info("Schema updated.");
116 126 }
117 127 break;
  128 + case "2.1.1":
  129 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  130 +
  131 + log.info("Dumping entity views ...");
  132 + Path entityViewsDump = SqlDbHelper.dumpTableIfExists(conn, ENTITY_VIEWS,
  133 + new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, TYPE, NAME, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO},
  134 + new String[]{"", "", "", "", "", "default", "", "", "0", "0", "", ""},
  135 + "tb-entity-views", true);
  136 + log.info("Entity views dumped.");
  137 +
  138 + log.info("Updating schema ...");
  139 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.2", SCHEMA_UPDATE_SQL);
  140 + loadSql(schemaUpdateFile, conn);
  141 + log.info("Schema updated.");
  142 +
  143 + log.info("Restoring entity views ...");
  144 + if (entityViewsDump != null) {
  145 + SqlDbHelper.loadTable(conn, ENTITY_VIEW,
  146 + new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, TYPE, NAME, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO}, entityViewsDump, true);
  147 + Files.deleteIfExists(entityViewsDump);
  148 + }
  149 + log.info("Entity views restored.");
  150 + }
  151 + break;
118 152
119 153 default:
120 154 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
... ...
... ... @@ -144,7 +144,9 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
144 144
145 145 @Test
146 146 public void testSaveEntityViewWithEmptyName() throws Exception {
147   - doPost("/api/entityView", new EntityView())
  147 + EntityView entityView = new EntityView();
  148 + entityView.setType("default");
  149 + doPost("/api/entityView", entityView)
148 150 .andExpect(status().isBadRequest())
149 151 .andExpect(statusReason(containsString("Entity view name should be specified!")));
150 152 }
... ... @@ -355,6 +357,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
355 357 view.setEntityId(testDevice.getId());
356 358 view.setTenantId(savedTenant.getId());
357 359 view.setName("Test entity view");
  360 + view.setType("default");
358 361 view.setKeys(telemetry);
359 362 view.setStartTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") * 10);
360 363 view.setEndTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") / 10);
... ... @@ -402,6 +405,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
402 405 view.setEntityId(testDevice.getId());
403 406 view.setTenantId(savedTenant.getId());
404 407 view.setName(name);
  408 + view.setType("default");
405 409 view.setKeys(telemetry);
406 410 return doPost("/api/entityView", view, EntityView.class);
407 411 }
... ...
... ... @@ -26,8 +26,8 @@ import java.util.Arrays;
26 26
27 27 @RunWith(ClasspathSuite.class)
28 28 @ClasspathSuite.ClassnameFilters({
29   - "org.thingsboard.server.rules.flow.nosql.*Test",
30   - "org.thingsboard.server.rules.lifecycle.nosql.*Test"
  29 + "org.thingsboard.server.rules.flow.nosql.RuleEngineFlowNoSqlIntegrationTest",
  30 +// "org.thingsboard.server.rules.lifecycle.nosql.*Test"
31 31 })
32 32 public class RuleEngineNoSqlTestSuite {
33 33
... ...
... ... @@ -40,6 +40,7 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
40 40 private TenantId tenantId;
41 41 private CustomerId customerId;
42 42 private String name;
  43 + private String type;
43 44 private TelemetryEntityView keys;
44 45 private long startTimeMs;
45 46 private long endTimeMs;
... ...
... ... @@ -30,6 +30,7 @@ public class EntityViewSearchQuery {
30 30
31 31 private RelationsSearchParameters parameters;
32 32 private String relationType;
  33 + private List<String> entityViewTypes;
33 34
34 35 public EntityRelationsQuery toEntitySearchQuery() {
35 36 EntityRelationsQuery query = new EntityRelationsQuery();
... ...
... ... @@ -15,8 +15,13 @@
15 15 */
16 16 package org.thingsboard.server.dao.entityview;
17 17
  18 +import com.datastax.driver.core.ResultSet;
  19 +import com.datastax.driver.core.ResultSetFuture;
18 20 import com.datastax.driver.core.Statement;
19 21 import com.datastax.driver.core.querybuilder.Select;
  22 +import com.datastax.driver.mapping.Result;
  23 +import com.google.common.base.Function;
  24 +import com.google.common.util.concurrent.Futures;
20 25 import com.google.common.util.concurrent.ListenableFuture;
21 26 import lombok.extern.slf4j.Slf4j;
22 27 import org.springframework.stereotype.Component;
... ... @@ -30,6 +35,8 @@ import org.thingsboard.server.dao.model.nosql.EntityViewEntity;
30 35 import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
31 36 import org.thingsboard.server.dao.util.NoSqlDao;
32 37
  38 +import javax.annotation.Nullable;
  39 +import java.util.ArrayList;
33 40 import java.util.Arrays;
34 41 import java.util.Collections;
35 42 import java.util.List;
... ... @@ -39,14 +46,21 @@ import java.util.UUID;
39 46 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
40 47 import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
41 48 import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_ID_PROPERTY;
  49 +import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TYPE_PROPERTY;
42 50 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN;
  51 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_COLUMN_FAMILY_NAME;
  52 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_ENTITY_TYPE_PROPERTY;
  53 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_TENANT_ID_PROPERTY;
  54 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_TYPE_CF;
43 55 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF;
44 56 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF;
45 57 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_NAME;
46   -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME;
  58 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_CF;
  59 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_CF;
47 60 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_NAME_PROPERTY;
48 61 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME;
49 62 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY;
  63 +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TYPE_PROPERTY;
50 64 import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_PROPERTY;
51 65
52 66 /**
... ... @@ -82,7 +96,7 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
82 96 public List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) {
83 97 log.debug("Try to find entity views by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
84 98 List<EntityViewEntity> entityViewEntities =
85   - findPageWithTextSearch(ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
  99 + findPageWithTextSearch(ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_CF,
86 100 Collections.singletonList(eq(TENANT_ID_PROPERTY, tenantId)), pageLink);
87 101 log.trace("Found entity views [{}] by tenantId [{}] and pageLink [{}]",
88 102 entityViewEntities, tenantId, pageLink);
... ... @@ -90,6 +104,18 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
90 104 }
91 105
92 106 @Override
  107 + public List<EntityView> findEntityViewsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) {
  108 + log.debug("Try to find entity views by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink);
  109 + List<EntityViewEntity> entityViewEntities =
  110 + findPageWithTextSearch(ENTITY_VIEW_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_CF,
  111 + Arrays.asList(eq(ENTITY_VIEW_TYPE_PROPERTY, type),
  112 + eq(TENANT_ID_PROPERTY, tenantId)), pageLink);
  113 + log.trace("Found entity views [{}] by tenantId [{}], type [{}] and pageLink [{}]",
  114 + entityViewEntities, tenantId, type, pageLink);
  115 + return DaoUtil.convertDataList(entityViewEntities);
  116 + }
  117 +
  118 + @Override
93 119 public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) {
94 120 Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_NAME).where();
95 121 query.and(eq(ENTITY_VIEW_TENANT_ID_PROPERTY, tenantId));
... ... @@ -111,6 +137,19 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
111 137 }
112 138
113 139 @Override
  140 + public List<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) {
  141 + log.debug("Try to find entity views by tenantId [{}], customerId[{}], type [{}] and pageLink [{}]",
  142 + tenantId, customerId, type, pageLink);
  143 + List<EntityViewEntity> entityViewEntities = findPageWithTextSearch(
  144 + ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_TYPE_CF,
  145 + Arrays.asList(eq(DEVICE_TYPE_PROPERTY, type), eq(CUSTOMER_ID_PROPERTY, customerId), eq(TENANT_ID_PROPERTY, tenantId)),
  146 + pageLink);
  147 + log.trace("Found find entity views [{}] by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]",
  148 + entityViewEntities, tenantId, customerId, type, pageLink);
  149 + return DaoUtil.convertDataList(entityViewEntities);
  150 + }
  151 +
  152 + @Override
114 153 public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) {
115 154 log.debug("Try to find entity views by tenantId [{}] and entityId [{}]", tenantId, entityId);
116 155 Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF).where();
... ... @@ -118,4 +157,30 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
118 157 query.and(eq(ENTITY_ID_COLUMN, entityId));
119 158 return findListByStatementAsync(query);
120 159 }
  160 +
  161 + @Override
  162 + public ListenableFuture<List<EntitySubtype>> findTenantEntityViewTypesAsync(UUID tenantId) {
  163 + Select select = select().from(ENTITY_SUBTYPE_COLUMN_FAMILY_NAME);
  164 + Select.Where query = select.where();
  165 + query.and(eq(ENTITY_SUBTYPE_TENANT_ID_PROPERTY, tenantId));
  166 + query.and(eq(ENTITY_SUBTYPE_ENTITY_TYPE_PROPERTY, EntityType.ENTITY_VIEW));
  167 + query.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel());
  168 + ResultSetFuture resultSetFuture = executeAsyncRead(query);
  169 + return Futures.transform(resultSetFuture, new Function<ResultSet, List<EntitySubtype>>() {
  170 + @Nullable
  171 + @Override
  172 + public List<EntitySubtype> apply(@Nullable ResultSet resultSet) {
  173 + Result<EntitySubtypeEntity> result = cluster.getMapper(EntitySubtypeEntity.class).map(resultSet);
  174 + if (result != null) {
  175 + List<EntitySubtype> entitySubtypes = new ArrayList<>();
  176 + result.all().forEach((entitySubtypeEntity) ->
  177 + entitySubtypes.add(entitySubtypeEntity.toEntitySubtype())
  178 + );
  179 + return entitySubtypes;
  180 + } else {
  181 + return Collections.emptyList();
  182 + }
  183 + }
  184 + });
  185 + }
121 186 }
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.entityview;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.Device;
  20 +import org.thingsboard.server.common.data.EntitySubtype;
20 21 import org.thingsboard.server.common.data.EntityView;
21 22 import org.thingsboard.server.common.data.page.TextPageLink;
22 23 import org.thingsboard.server.dao.Dao;
... ... @@ -48,6 +49,16 @@ public interface EntityViewDao extends Dao<EntityView> {
48 49 List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink);
49 50
50 51 /**
  52 + * Find entity views by tenantId, type and page link.
  53 + *
  54 + * @param tenantId the tenantId
  55 + * @param type the type
  56 + * @param pageLink the page link
  57 + * @return the list of entity view objects
  58 + */
  59 + List<EntityView> findEntityViewsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink);
  60 +
  61 + /**
51 62 * Find entity views by tenantId and entity view name.
52 63 *
53 64 * @param tenantId the tenantId
... ... @@ -68,6 +79,27 @@ public interface EntityViewDao extends Dao<EntityView> {
68 79 UUID customerId,
69 80 TextPageLink pageLink);
70 81
  82 + /**
  83 + * Find entity views by tenantId, customerId, type and page link.
  84 + *
  85 + * @param tenantId the tenantId
  86 + * @param customerId the customerId
  87 + * @param type the type
  88 + * @param pageLink the page link
  89 + * @return the list of entity view objects
  90 + */
  91 + List<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId,
  92 + UUID customerId,
  93 + String type,
  94 + TextPageLink pageLink);
71 95
72 96 ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId);
  97 +
  98 + /**
  99 + * Find tenants entity view types.
  100 + *
  101 + * @return the list of tenant entity view type objects
  102 + */
  103 + ListenableFuture<List<EntitySubtype>> findTenantEntityViewTypesAsync(UUID tenantId);
  104 +
73 105 }
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.dao.entityview;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.EntitySubtype;
19 20 import org.thingsboard.server.common.data.EntityView;
20 21 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
21 22 import org.thingsboard.server.common.data.id.CustomerId;
... ... @@ -44,8 +45,12 @@ public interface EntityViewService {
44 45
45 46 TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink);
46 47
  48 + TextPageData<EntityView> findEntityViewByTenantIdAndType(TenantId tenantId, TextPageLink pageLink, String type);
  49 +
47 50 TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
48 51
  52 + TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, TextPageLink pageLink, String type);
  53 +
49 54 ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query);
50 55
51 56 ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId);
... ... @@ -55,4 +60,6 @@ public interface EntityViewService {
55 60 void deleteEntityView(EntityViewId entityViewId);
56 61
57 62 void deleteEntityViewsByTenantId(TenantId tenantId);
  63 +
  64 + ListenableFuture<List<EntitySubtype>> findEntityViewTypesByTenantId(TenantId tenantId);
58 65 }
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.dao.entityview;
17 17
  18 +import com.google.common.base.Function;
18 19 import com.google.common.util.concurrent.FutureCallback;
19 20 import com.google.common.util.concurrent.Futures;
20 21 import com.google.common.util.concurrent.ListenableFuture;
... ... @@ -29,6 +30,8 @@ import org.springframework.cache.annotation.Caching;
29 30 import org.springframework.stereotype.Service;
30 31 import org.thingsboard.server.common.data.Customer;
31 32 import org.thingsboard.server.common.data.DataConstants;
  33 +import org.thingsboard.server.common.data.Device;
  34 +import org.thingsboard.server.common.data.EntitySubtype;
32 35 import org.thingsboard.server.common.data.EntityType;
33 36 import org.thingsboard.server.common.data.EntityView;
34 37 import org.thingsboard.server.common.data.Tenant;
... ... @@ -54,6 +57,8 @@ import javax.annotation.Nullable;
54 57 import java.util.ArrayList;
55 58 import java.util.Arrays;
56 59 import java.util.Collection;
  60 +import java.util.Collections;
  61 +import java.util.Comparator;
57 62 import java.util.List;
58 63 import java.util.concurrent.ExecutionException;
59 64 import java.util.stream.Collectors;
... ... @@ -63,6 +68,7 @@ import static org.thingsboard.server.common.data.CacheConstants.RELATIONS_CACHE;
63 68 import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
64 69 import static org.thingsboard.server.dao.service.Validator.validateId;
65 70 import static org.thingsboard.server.dao.service.Validator.validatePageLink;
  71 +import static org.thingsboard.server.dao.service.Validator.validateString;
66 72
67 73 /**
68 74 * Created by Victor Basanets on 8/28/2017.
... ... @@ -158,6 +164,16 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
158 164 }
159 165
160 166 @Override
  167 + public TextPageData<EntityView> findEntityViewByTenantIdAndType(TenantId tenantId, TextPageLink pageLink, String type) {
  168 + log.trace("Executing findEntityViewByTenantIdAndType, tenantId [{}], pageLink [{}], type [{}]", tenantId, pageLink, type);
  169 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  170 + validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
  171 + validateString(type, "Incorrect type " + type);
  172 + List<EntityView> entityViews = entityViewDao.findEntityViewsByTenantIdAndType(tenantId.getId(), type, pageLink);
  173 + return new TextPageData<>(entityViews, pageLink);
  174 + }
  175 +
  176 + @Override
161 177 public TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId,
162 178 TextPageLink pageLink) {
163 179 log.trace("Executing findEntityViewByTenantIdAndCustomerId, tenantId [{}], customerId [{}]," +
... ... @@ -171,6 +187,19 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
171 187 }
172 188
173 189 @Override
  190 + public TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, TextPageLink pageLink, String type) {
  191 + log.trace("Executing findEntityViewsByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}]," +
  192 + " pageLink [{}], type [{}]", tenantId, customerId, pageLink, type);
  193 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  194 + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId);
  195 + validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
  196 + validateString(type, "Incorrect type " + type);
  197 + List<EntityView> entityViews = entityViewDao.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId.getId(),
  198 + customerId.getId(), type, pageLink);
  199 + return new TextPageData<>(entityViews, pageLink);
  200 + }
  201 +
  202 + @Override
174 203 public ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query) {
175 204 ListenableFuture<List<EntityRelation>> relations = relationService.findByQuery(query.toEntitySearchQuery());
176 205 ListenableFuture<List<EntityView>> entityViews = Futures.transformAsync(relations, r -> {
... ... @@ -184,6 +213,15 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
184 213 }
185 214 return Futures.successfulAsList(futures);
186 215 });
  216 +
  217 + entityViews = Futures.transform(entityViews, new Function<List<EntityView>, List<EntityView>>() {
  218 + @Nullable
  219 + @Override
  220 + public List<EntityView> apply(@Nullable List<EntityView> entityViewList) {
  221 + return entityViewList == null ? Collections.emptyList() : entityViewList.stream().filter(entityView -> query.getEntityViewTypes().contains(entityView.getType())).collect(Collectors.toList());
  222 + }
  223 + });
  224 +
187 225 return entityViews;
188 226 }
189 227
... ... @@ -216,6 +254,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
216 254 public void onSuccess(@Nullable List<EntityView> result) {
217 255 cache.putIfAbsent(tenantIdAndEntityId, result);
218 256 }
  257 +
219 258 @Override
220 259 public void onFailure(Throwable t) {
221 260 log.error("Error while finding entity views by tenantId and entityId", t);
... ... @@ -243,6 +282,18 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
243 282 tenantEntityViewRemover.removeEntities(tenantId);
244 283 }
245 284
  285 + @Override
  286 + public ListenableFuture<List<EntitySubtype>> findEntityViewTypesByTenantId(TenantId tenantId) {
  287 + log.trace("Executing findEntityViewTypesByTenantId, tenantId [{}]", tenantId);
  288 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  289 + ListenableFuture<List<EntitySubtype>> tenantEntityViewTypes = entityViewDao.findTenantEntityViewTypesAsync(tenantId.getId());
  290 + return Futures.transform(tenantEntityViewTypes,
  291 + entityViewTypes -> {
  292 + entityViewTypes.sort(Comparator.comparing(EntitySubtype::getType));
  293 + return entityViewTypes;
  294 + });
  295 + }
  296 +
246 297 private ListenableFuture<List<Void>> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection<String> keys) {
247 298 if (keys != null && !keys.isEmpty()) {
248 299 ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys);
... ... @@ -296,6 +347,9 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
296 347
297 348 @Override
298 349 protected void validateDataImpl(EntityView entityView) {
  350 + if (StringUtils.isEmpty(entityView.getType())) {
  351 + throw new DataValidationException("Entity View type should be specified!");
  352 + }
299 353 if (StringUtils.isEmpty(entityView.getName())) {
300 354 throw new DataValidationException("Entity view name should be specified!");
301 355 }
... ...
... ... @@ -145,18 +145,21 @@ public class ModelConstants {
145 145 /**
146 146 * Cassandra entityView constants.
147 147 */
148   - public static final String ENTITY_VIEW_TABLE_FAMILY_NAME = "entity_views";
  148 + public static final String ENTITY_VIEW_TABLE_FAMILY_NAME = "entity_view";
149 149 public static final String ENTITY_VIEW_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN;
150 150 public static final String ENTITY_VIEW_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
151 151 public static final String ENTITY_VIEW_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
152 152 public static final String ENTITY_VIEW_NAME_PROPERTY = DEVICE_NAME_PROPERTY;
153 153 public static final String ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF = "entity_view_by_tenant_and_customer";
  154 + public static final String ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_TYPE_CF = "entity_view_by_tenant_and_customer_and_type";
154 155 public static final String ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF = "entity_view_by_tenant_and_entity_id";
155 156 public static final String ENTITY_VIEW_KEYS_PROPERTY = "keys";
  157 + public static final String ENTITY_VIEW_TYPE_PROPERTY = "type";
156 158 public static final String ENTITY_VIEW_START_TS_PROPERTY = "start_ts";
157 159 public static final String ENTITY_VIEW_END_TS_PROPERTY = "end_ts";
158 160 public static final String ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
159   - public static final String ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "entity_view_by_tenant_and_search_text";
  161 + public static final String ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_CF = "entity_view_by_tenant_and_search_text";
  162 + public static final String ENTITY_VIEW_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_CF = "entity_view_by_tenant_by_type_and_search_text";
160 163 public static final String ENTITY_VIEW_BY_TENANT_AND_NAME = "entity_view_by_tenant_and_name";
161 164
162 165 /**
... ...
... ... @@ -41,6 +41,7 @@ import javax.persistence.Enumerated;
41 41 import java.io.IOException;
42 42 import java.util.UUID;
43 43
  44 +import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TYPE_PROPERTY;
44 45 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY;
45 46 import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME;
46 47 import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
... ... @@ -71,6 +72,10 @@ public class EntityViewEntity implements SearchTextEntity<EntityView> {
71 72 @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY)
72 73 private UUID customerId;
73 74
  75 + @PartitionKey(value = 3)
  76 + @Column(name = DEVICE_TYPE_PROPERTY)
  77 + private String type;
  78 +
74 79 @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY)
75 80 private UUID entityId;
76 81
... ... @@ -113,6 +118,7 @@ public class EntityViewEntity implements SearchTextEntity<EntityView> {
113 118 if (entityView.getCustomerId() != null) {
114 119 this.customerId = entityView.getCustomerId().getId();
115 120 }
  121 + this.type = entityView.getType();
116 122 this.name = entityView.getName();
117 123 try {
118 124 this.keys = mapper.writeValueAsString(entityView.getKeys());
... ... @@ -143,6 +149,7 @@ public class EntityViewEntity implements SearchTextEntity<EntityView> {
143 149 if (customerId != null) {
144 150 entityView.setCustomerId(new CustomerId(customerId));
145 151 }
  152 + entityView.setType(type);
146 153 entityView.setName(name);
147 154 try {
148 155 entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class));
... ...
... ... @@ -69,6 +69,9 @@ public class EntityViewEntity extends BaseSqlEntity<EntityView> implements Searc
69 69 @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY)
70 70 private String customerId;
71 71
  72 + @Column(name = ModelConstants.DEVICE_TYPE_PROPERTY)
  73 + private String type;
  74 +
72 75 @Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY)
73 76 private String name;
74 77
... ... @@ -108,6 +111,7 @@ public class EntityViewEntity extends BaseSqlEntity<EntityView> implements Searc
108 111 if (entityView.getCustomerId() != null) {
109 112 this.customerId = toString(entityView.getCustomerId().getId());
110 113 }
  114 + this.type = entityView.getType();
111 115 this.name = entityView.getName();
112 116 try {
113 117 this.keys = mapper.writeValueAsString(entityView.getKeys());
... ... @@ -144,6 +148,7 @@ public class EntityViewEntity extends BaseSqlEntity<EntityView> implements Searc
144 148 if (customerId != null) {
145 149 entityView.setCustomerId(new CustomerId(toUUID(customerId)));
146 150 }
  151 + entityView.setType(type);
147 152 entityView.setName(name);
148 153 try {
149 154 entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class));
... ...
... ... @@ -19,8 +19,6 @@ import org.springframework.data.domain.Pageable;
19 19 import org.springframework.data.jpa.repository.Query;
20 20 import org.springframework.data.repository.CrudRepository;
21 21 import org.springframework.data.repository.query.Param;
22   -import org.thingsboard.server.common.data.EntityView;
23   -import org.thingsboard.server.common.data.id.EntityId;
24 22 import org.thingsboard.server.dao.model.sql.EntityViewEntity;
25 23 import org.thingsboard.server.dao.util.SqlDao;
26 24
... ... @@ -36,21 +34,46 @@ public interface EntityViewRepository extends CrudRepository<EntityViewEntity, S
36 34 "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " +
37 35 "AND e.id > :idOffset ORDER BY e.id")
38 36 List<EntityViewEntity> findByTenantId(@Param("tenantId") String tenantId,
39   - @Param("textSearch") String textSearch,
40   - @Param("idOffset") String idOffset,
41   - Pageable pageable);
  37 + @Param("textSearch") String textSearch,
  38 + @Param("idOffset") String idOffset,
  39 + Pageable pageable);
  40 +
  41 + @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
  42 + "AND e.type = :type " +
  43 + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " +
  44 + "AND e.id > :idOffset ORDER BY e.id")
  45 + List<EntityViewEntity> findByTenantIdAndType(@Param("tenantId") String tenantId,
  46 + @Param("type") String type,
  47 + @Param("textSearch") String textSearch,
  48 + @Param("idOffset") String idOffset,
  49 + Pageable pageable);
42 50
43 51 @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
44 52 "AND e.customerId = :customerId " +
45 53 "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
46 54 "AND e.id > :idOffset ORDER BY e.id")
47 55 List<EntityViewEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
48   - @Param("customerId") String customerId,
49   - @Param("searchText") String searchText,
50   - @Param("idOffset") String idOffset,
51   - Pageable pageable);
  56 + @Param("customerId") String customerId,
  57 + @Param("searchText") String searchText,
  58 + @Param("idOffset") String idOffset,
  59 + Pageable pageable);
  60 +
  61 + @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
  62 + "AND e.customerId = :customerId " +
  63 + "AND e.type = :type " +
  64 + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
  65 + "AND e.id > :idOffset ORDER BY e.id")
  66 + List<EntityViewEntity> findByTenantIdAndCustomerIdAndType(@Param("tenantId") String tenantId,
  67 + @Param("customerId") String customerId,
  68 + @Param("type") String type,
  69 + @Param("searchText") String searchText,
  70 + @Param("idOffset") String idOffset,
  71 + Pageable pageable);
52 72
53 73 EntityViewEntity findByTenantIdAndName(String tenantId, String name);
54 74
55 75 List<EntityViewEntity> findAllByTenantIdAndEntityId(String tenantId, String entityId);
  76 +
  77 + @Query("SELECT DISTINCT ev.type FROM EntityViewEntity ev WHERE ev.tenantId = :tenantId")
  78 + List<String> findTenantEntityViewTypes(@Param("tenantId") String tenantId);
56 79 }
... ...
... ... @@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.EntitySubtype;
24 24 import org.thingsboard.server.common.data.EntityType;
25 25 import org.thingsboard.server.common.data.EntityView;
26 26 import org.thingsboard.server.common.data.UUIDConverter;
27   -import org.thingsboard.server.common.data.id.EntityId;
28 27 import org.thingsboard.server.common.data.id.TenantId;
29 28 import org.thingsboard.server.common.data.page.TextPageLink;
30 29 import org.thingsboard.server.dao.DaoUtil;
... ... @@ -41,7 +40,6 @@ import java.util.Optional;
41 40 import java.util.UUID;
42 41
43 42 import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
44   -import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUIDs;
45 43 import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
46 44
47 45 /**
... ... @@ -76,6 +74,17 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao<EntityViewEntity,
76 74 }
77 75
78 76 @Override
  77 + public List<EntityView> findEntityViewsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) {
  78 + return DaoUtil.convertDataList(
  79 + entityViewRepository.findByTenantIdAndType(
  80 + fromTimeUUID(tenantId),
  81 + type,
  82 + Objects.toString(pageLink.getTextSearch(), ""),
  83 + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
  84 + new PageRequest(0, pageLink.getLimit())));
  85 + }
  86 +
  87 + @Override
79 88 public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) {
80 89 return Optional.ofNullable(
81 90 DaoUtil.getData(entityViewRepository.findByTenantIdAndName(fromTimeUUID(tenantId), name)));
... ... @@ -96,8 +105,37 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao<EntityViewEntity,
96 105 }
97 106
98 107 @Override
  108 + public List<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) {
  109 + return DaoUtil.convertDataList(
  110 + entityViewRepository.findByTenantIdAndCustomerIdAndType(
  111 + fromTimeUUID(tenantId),
  112 + fromTimeUUID(customerId),
  113 + type,
  114 + Objects.toString(pageLink.getTextSearch(), ""),
  115 + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
  116 + new PageRequest(0, pageLink.getLimit())
  117 + ));
  118 + }
  119 +
  120 + @Override
99 121 public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) {
100 122 return service.submit(() -> DaoUtil.convertDataList(
101 123 entityViewRepository.findAllByTenantIdAndEntityId(UUIDConverter.fromTimeUUID(tenantId), UUIDConverter.fromTimeUUID(entityId))));
102 124 }
  125 +
  126 + @Override
  127 + public ListenableFuture<List<EntitySubtype>> findTenantEntityViewTypesAsync(UUID tenantId) {
  128 + return service.submit(() -> convertTenantEntityViewTypesToDto(tenantId, entityViewRepository.findTenantEntityViewTypes(fromTimeUUID(tenantId))));
  129 + }
  130 +
  131 + private List<EntitySubtype> convertTenantEntityViewTypesToDto(UUID tenantId, List<String> types) {
  132 + List<EntitySubtype> list = Collections.emptyList();
  133 + if (types != null && !types.isEmpty()) {
  134 + list = new ArrayList<>();
  135 + for (String type : types) {
  136 + list.add(new EntitySubtype(new TenantId(tenantId), EntityType.ENTITY_VIEW, type));
  137 + }
  138 + }
  139 + return list;
  140 + }
103 141 }
... ...
... ... @@ -624,61 +624,90 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
624 624 PRIMARY KEY (id)
625 625 );
626 626
627   -CREATE TABLE IF NOT EXISTS thingsboard.entity_views (
  627 +CREATE TABLE IF NOT EXISTS thingsboard.entity_view (
628 628 id timeuuid,
629 629 entity_id timeuuid,
630 630 entity_type text,
631 631 tenant_id timeuuid,
632 632 customer_id timeuuid,
633 633 name text,
  634 + type text,
634 635 keys text,
635 636 start_ts bigint,
636 637 end_ts bigint,
637 638 search_text text,
638 639 additional_info text,
639   - PRIMARY KEY (id, entity_id, tenant_id, customer_id)
  640 + PRIMARY KEY (id, entity_id, tenant_id, customer_id, type)
640 641 );
641 642
642 643 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS
643 644 SELECT *
644   - from thingsboard.entity_views
  645 + from thingsboard.entity_view
645 646 WHERE tenant_id IS NOT NULL
646 647 AND entity_id IS NOT NULL
647 648 AND customer_id IS NOT NULL
  649 + AND type IS NOT NULL
648 650 AND name IS NOT NULL
649 651 AND id IS NOT NULL
650   - PRIMARY KEY (tenant_id, name, id, customer_id, entity_id)
  652 + PRIMARY KEY (tenant_id, name, id, customer_id, entity_id, type)
651 653 WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC);
652 654
653 655 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS
654 656 SELECT *
655   - from thingsboard.entity_views
  657 + from thingsboard.entity_view
656 658 WHERE tenant_id IS NOT NULL
657 659 AND entity_id IS NOT NULL
658 660 AND customer_id IS NOT NULL
  661 + AND type IS NOT NULL
659 662 AND search_text IS NOT NULL
660 663 AND id IS NOT NULL
661   - PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id)
  664 + PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id, type)
662 665 WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC);
663 666
  667 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_by_type_and_search_text AS
  668 + SELECT *
  669 + from thingsboard.entity_view
  670 + WHERE tenant_id IS NOT NULL
  671 + AND entity_id IS NOT NULL
  672 + AND customer_id IS NOT NULL
  673 + AND type IS NOT NULL
  674 + AND search_text IS NOT NULL
  675 + AND id IS NOT NULL
  676 + PRIMARY KEY (tenant_id, type, search_text, id, customer_id, entity_id)
  677 + WITH CLUSTERING ORDER BY (type ASC, search_text ASC, id DESC, customer_id DESC);
  678 +
664 679 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS
665 680 SELECT *
666   - from thingsboard.entity_views
  681 + from thingsboard.entity_view
667 682 WHERE tenant_id IS NOT NULL
668 683 AND customer_id IS NOT NULL
669 684 AND entity_id IS NOT NULL
  685 + AND type IS NOT NULL
670 686 AND search_text IS NOT NULL
671 687 AND id IS NOT NULL
672   - PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id)
  688 + PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id, type)
673 689 WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC);
674 690
  691 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer_and_type AS
  692 + SELECT *
  693 + from thingsboard.entity_view
  694 + WHERE tenant_id IS NOT NULL
  695 + AND customer_id IS NOT NULL
  696 + AND entity_id IS NOT NULL
  697 + AND type IS NOT NULL
  698 + AND search_text IS NOT NULL
  699 + AND id IS NOT NULL
  700 + PRIMARY KEY (tenant_id, type, customer_id, search_text, id, entity_id)
  701 + WITH CLUSTERING ORDER BY (type ASC, customer_id DESC, search_text ASC, id DESC);
  702 +
675 703 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS
676 704 SELECT *
677   - from thingsboard.entity_views
  705 + from thingsboard.entity_view
678 706 WHERE tenant_id IS NOT NULL
679 707 AND customer_id IS NOT NULL
680 708 AND entity_id IS NOT NULL
  709 + AND type IS NOT NULL
681 710 AND search_text IS NOT NULL
682 711 AND id IS NOT NULL
683   - PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id)
  712 + PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id, type)
684 713 WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);
\ No newline at end of file
... ...
... ... @@ -228,12 +228,13 @@ CREATE TABLE IF NOT EXISTS rule_node (
228 228 search_text varchar(255)
229 229 );
230 230
231   -CREATE TABLE IF NOT EXISTS entity_views (
  231 +CREATE TABLE IF NOT EXISTS entity_view (
232 232 id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
233 233 entity_id varchar(31),
234 234 entity_type varchar(255),
235 235 tenant_id varchar(31),
236 236 customer_id varchar(31),
  237 + type varchar(255),
237 238 name varchar(255),
238 239 keys varchar(255),
239 240 start_ts bigint,
... ...
... ... @@ -19,4 +19,4 @@ DROP TABLE IF EXISTS widget_type;
19 19 DROP TABLE IF EXISTS widgets_bundle;
20 20 DROP TABLE IF EXISTS rule_node;
21 21 DROP TABLE IF EXISTS rule_chain;
22   -DROP TABLE IF EXISTS entity_views;
  22 +DROP TABLE IF EXISTS entity_view;
... ...
... ... @@ -533,6 +533,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
533 533 }
534 534 );
535 535 break;
  536 + case types.aliasFilterType.entityViewType.value:
  537 + getEntitiesByNameFilter(types.entityType.entityView, filter.entityViewNameFilter, maxItems, {ignoreLoading: true}, filter.entityViewType).then(
  538 + function success(entities) {
  539 + if (entities && entities.length || !failOnEmpty) {
  540 + result.entities = entitiesToEntitiesInfo(entities);
  541 + deferred.resolve(result);
  542 + } else {
  543 + deferred.reject();
  544 + }
  545 + },
  546 + function fail() {
  547 + deferred.reject();
  548 + }
  549 + );
  550 + break;
536 551 case types.aliasFilterType.relationsQuery.value:
537 552 result.stateEntity = filter.rootStateEntity;
538 553 var rootEntityType;
... ... @@ -578,6 +593,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
578 593 break;
579 594 case types.aliasFilterType.assetSearchQuery.value:
580 595 case types.aliasFilterType.deviceSearchQuery.value:
  596 + case types.aliasFilterType.entityViewSearchQuery.value:
581 597 result.stateEntity = filter.rootStateEntity;
582 598 if (result.stateEntity && stateEntityId) {
583 599 rootEntityType = stateEntityId.entityType;
... ... @@ -604,6 +620,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
604 620 } else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) {
605 621 searchQuery.deviceTypes = filter.deviceTypes;
606 622 findByQueryPromise = deviceService.findByQuery(searchQuery, false, {ignoreLoading: true});
  623 + } else if (filter.type == types.aliasFilterType.entityViewSearchQuery.value) {
  624 + searchQuery.entityViewTypes = filter.entityViewTypes;
  625 + findByQueryPromise = entityViewService.findByQuery(searchQuery, false, {ignoreLoading: true});
607 626 }
608 627 findByQueryPromise.then(
609 628 function success(entities) {
... ... @@ -646,6 +665,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
646 665 return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
647 666 case types.aliasFilterType.deviceType.value:
648 667 return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
  668 + case types.aliasFilterType.entityViewType.value:
  669 + return entityTypes.indexOf(types.entityType.entityView) > -1 ? true : false;
649 670 case types.aliasFilterType.relationsQuery.value:
650 671 if (filter.filters && filter.filters.length) {
651 672 var match = false;
... ... @@ -671,6 +692,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
671 692 return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
672 693 case types.aliasFilterType.deviceSearchQuery.value:
673 694 return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
  695 + case types.aliasFilterType.entityViewSearchQuery.value:
  696 + return entityTypes.indexOf(types.entityType.entityView) > -1 ? true : false;
674 697 }
675 698 }
676 699 return false;
... ... @@ -690,12 +713,16 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
690 713 return entityType === types.entityType.asset;
691 714 case types.aliasFilterType.deviceType.value:
692 715 return entityType === types.entityType.device;
  716 + case types.aliasFilterType.entityViewType.value:
  717 + return entityType === types.entityType.entityView;
693 718 case types.aliasFilterType.relationsQuery.value:
694 719 return true;
695 720 case types.aliasFilterType.assetSearchQuery.value:
696 721 return entityType === types.entityType.asset;
697 722 case types.aliasFilterType.deviceSearchQuery.value:
698 723 return entityType === types.entityType.device;
  724 + case types.aliasFilterType.entityViewSearchQuery.value:
  725 + return entityType === types.entityType.entityView;
699 726 }
700 727 return false;
701 728 }
... ... @@ -1046,6 +1073,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
1046 1073 return assetService.deleteAsset(entityId.id);
1047 1074 } else if (entityId.entityType == types.entityType.device) {
1048 1075 return deviceService.deleteDevice(entityId.id);
  1076 + } else if (entityId.entityType == types.entityType.entityView) {
  1077 + return entityViewService.deleteEntityView(entityId.id);
1049 1078 }
1050 1079 }
1051 1080
... ... @@ -1151,6 +1180,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
1151 1180 return assetService.saveAsset(entity);
1152 1181 } else if (entityType == types.entityType.device) {
1153 1182 return deviceService.saveDevice(entity);
  1183 + } else if (entityType == types.entityType.entityView) {
  1184 + return entityViewService.saveEntityView(entity);
1154 1185 }
1155 1186 }
1156 1187
... ... @@ -1279,6 +1310,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
1279 1310 searchQuery.assetTypes = entitySubTypes;
1280 1311 } else if (entityType == types.entityType.device) {
1281 1312 searchQuery.deviceTypes = entitySubTypes;
  1313 + } else if (entityType == types.entityType.entityView) {
  1314 + searchQuery.entityViewTypes = entitySubTypes;
1282 1315 } else {
1283 1316 return null; //Not supported
1284 1317 }
... ...
... ... @@ -253,6 +253,10 @@ export default angular.module('thingsboard.types', [])
253 253 value: 'deviceType',
254 254 name: 'alias.filter-type-device-type'
255 255 },
  256 + entityViewType: {
  257 + value: 'entityViewType',
  258 + name: 'alias.filter-type-entity-view-type'
  259 + },
256 260 relationsQuery: {
257 261 value: 'relationsQuery',
258 262 name: 'alias.filter-type-relations-query'
... ... @@ -264,6 +268,10 @@ export default angular.module('thingsboard.types', [])
264 268 deviceSearchQuery: {
265 269 value: 'deviceSearchQuery',
266 270 name: 'alias.filter-type-device-search-query'
  271 + },
  272 + entityViewSearchQuery: {
  273 + value: 'entityViewSearchQuery',
  274 + name: 'alias.filter-type-entity-view-search-query'
267 275 }
268 276 },
269 277 position: {
... ...
... ... @@ -52,6 +52,13 @@
52 52 <div translate ng-message="required">entity-view.name-required</div>
53 53 </div>
54 54 </md-input-container>
  55 + <tb-entity-subtype-autocomplete
  56 + ng-disabled="$root.loading || !isEdit"
  57 + tb-required="true"
  58 + the-form="theForm"
  59 + ng-model="entityView.type"
  60 + entity-type="types.entityType.entityView">
  61 + </tb-entity-subtype-autocomplete>
55 62 <tb-entity-select flex ng-disabled="!isEdit"
56 63 the-form="theForm"
57 64 tb-required="true"
... ...
... ... @@ -77,6 +77,15 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q,
77 77 scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType});
78 78 }
79 79 break;
  80 + case types.aliasFilterType.entityViewType.value:
  81 + var entityViewType = scope.filter.entityViewType;
  82 + prefix = scope.filter.entityViewNameFilter;
  83 + if (prefix && prefix.length) {
  84 + scope.filterDisplayValue = $translate.instant('alias.filter-type-entity-view-type-and-name-description', {entityViewType: entityViewType, prefix: prefix});
  85 + } else {
  86 + scope.filterDisplayValue = $translate.instant('alias.filter-type-entity-view-type-description', {entityViewType: entityViewType});
  87 + }
  88 + break;
80 89 case types.aliasFilterType.relationsQuery.value:
81 90 var rootEntityText;
82 91 var directionText;
... ... @@ -134,6 +143,7 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q,
134 143 break;
135 144 case types.aliasFilterType.assetSearchQuery.value:
136 145 case types.aliasFilterType.deviceSearchQuery.value:
  146 + case types.aliasFilterType.entityViewSearchQuery.value:
137 147 allEntitiesText = $translate.instant('alias.all-entities');
138 148 anyRelationText = $translate.instant('alias.any-relation');
139 149 if (scope.filter.rootStateEntity) {
... ... @@ -165,7 +175,7 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q,
165 175 scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-search-query-description',
166 176 translationValues
167 177 );
168   - } else {
  178 + } else if (scope.filter.type == types.aliasFilterType.deviceSearchQuery.value) {
169 179 var deviceTypesQuoted = [];
170 180 scope.filter.deviceTypes.forEach(function(deviceType) {
171 181 deviceTypesQuoted.push("'"+deviceType+"'");
... ... @@ -175,6 +185,16 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q,
175 185 scope.filterDisplayValue = $translate.instant('alias.filter-type-device-search-query-description',
176 186 translationValues
177 187 );
  188 + } else if (scope.filter.type == types.aliasFilterType.entityViewSearchQuery.value) {
  189 + var entityViewTypesQuoted = [];
  190 + scope.filter.entityViewTypes.forEach(function(entityViewType) {
  191 + entityViewTypesQuoted.push("'"+entityViewType+"'");
  192 + });
  193 + var entityViewTypesText = entityViewTypesQuoted.join(', ');
  194 + translationValues.entityViewTypes = entityViewTypesText;
  195 + scope.filterDisplayValue = $translate.instant('alias.filter-type-entity-view-search-query-description',
  196 + translationValues
  197 + );
178 198 }
179 199 break;
180 200 default:
... ...
... ... @@ -69,9 +69,14 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
69 69 filter.deviceType = null;
70 70 filter.deviceNameFilter = '';
71 71 break;
  72 + case types.aliasFilterType.entityViewType.value:
  73 + filter.entityViewType = null;
  74 + filter.entityViewNameFilter = '';
  75 + break;
72 76 case types.aliasFilterType.relationsQuery.value:
73 77 case types.aliasFilterType.assetSearchQuery.value:
74 78 case types.aliasFilterType.deviceSearchQuery.value:
  79 + case types.aliasFilterType.entityViewSearchQuery.value:
75 80 filter.rootStateEntity = false;
76 81 filter.stateEntityParamName = null;
77 82 filter.defaultStateEntity = null;
... ... @@ -86,6 +91,9 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
86 91 } else if (filter.type === types.aliasFilterType.deviceSearchQuery.value) {
87 92 filter.relationType = null;
88 93 filter.deviceTypes = [];
  94 + } else if (filter.type === types.aliasFilterType.entityViewSearchQuery.value) {
  95 + filter.relationType = null;
  96 + filter.entityViewTypes = [];
89 97 }
90 98 break;
91 99 }
... ...
... ... @@ -112,6 +112,20 @@
112 112 aria-label="{{ 'device.name-starts-with' | translate }}">
113 113 </md-input-container>
114 114 </section>
  115 + <section layout="column" ng-if="filter.type == types.aliasFilterType.entityViewType.value" id="entityViewTypeFilter">
  116 + <tb-entity-subtype-autocomplete
  117 + tb-required="true"
  118 + the-form="theForm"
  119 + ng-model="filter.entityViewType"
  120 + entity-type="types.entityType.entityView">
  121 + </tb-entity-subtype-autocomplete>
  122 + <md-input-container class="md-block">
  123 + <label translate>entity-view.name-starts-with</label>
  124 + <input name="entityViewNameFilter"
  125 + ng-model="filter.entityViewNameFilter"
  126 + aria-label="{{ 'entity-view.name-starts-with' | translate }}">
  127 + </md-input-container>
  128 + </section>
115 129 <section layout="column" ng-if="filter.type == types.aliasFilterType.relationsQuery.value" id="relationsQueryFilter">
116 130 <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
117 131 <section class="tb-root-state-entity-switch" layout="row" layout-align="start center" style="padding-left: 0px;">
... ... @@ -311,4 +325,73 @@
311 325 ng-model="filter.deviceTypes">
312 326 </tb-entity-subtype-list>
313 327 </section>
  328 + <section layout="column" ng-if="filter.type == types.aliasFilterType.entityViewSearchQuery.value" id="entityViewSearchQueryFilter">
  329 + <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
  330 + <section class="tb-root-state-entity-switch" layout="row" layout-align="start center" style="padding-left: 0px;">
  331 + <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
  332 + aria-label="{{ 'alias.root-state-entity' | translate }}">
  333 + </md-switch>
  334 + <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
  335 + </section>
  336 + <div flex layout="row" ng-if="!filter.rootStateEntity">
  337 + <tb-entity-select flex
  338 + the-form="theForm"
  339 + tb-required="!filter.rootStateEntity"
  340 + ng-disabled="filter.rootStateEntity"
  341 + use-alias-entity-types="true"
  342 + ng-model="filter.rootEntity">
  343 + </tb-entity-select>
  344 + </div>
  345 + <div flex layout="row" ng-if="filter.rootStateEntity">
  346 + <md-input-container class="md-block" style="margin-top: 32px;">
  347 + <label translate>alias.state-entity-parameter-name</label>
  348 + <input name="stateEntityParamName"
  349 + placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
  350 + ng-model="filter.stateEntityParamName"
  351 + aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
  352 + </md-input-container>
  353 + <div flex layout="column">
  354 + <label class="tb-small">{{ 'alias.default-state-entity' | translate }}</label>
  355 + <tb-entity-select flex
  356 + the-form="theForm"
  357 + tb-required="false"
  358 + use-alias-entity-types="true"
  359 + ng-model="filter.defaultStateEntity">
  360 + </tb-entity-select>
  361 + </div>
  362 + </div>
  363 + <div flex layout="row">
  364 + <md-input-container class="md-block" style="min-width: 100px;">
  365 + <label translate>relation.direction</label>
  366 + <md-select required ng-model="filter.direction">
  367 + <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
  368 + {{ ('relation.search-direction.' + direction) | translate}}
  369 + </md-option>
  370 + </md-select>
  371 + </md-input-container>
  372 + <md-input-container flex class="md-block">
  373 + <label translate>alias.max-relation-level</label>
  374 + <input name="maxRelationLevel"
  375 + type="number"
  376 + min="1"
  377 + step="1"
  378 + placeholder="{{ 'alias.unlimited-level' | translate }}"
  379 + ng-model="filter.maxLevel"
  380 + aria-label="{{ 'alias.max-relation-level' | translate }}">
  381 + </md-input-container>
  382 + </div>
  383 + <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div>
  384 + <tb-relation-type-autocomplete flex
  385 + hide-label
  386 + the-form="theForm"
  387 + ng-model="filter.relationType"
  388 + tb-required="false">
  389 + </tb-relation-type-autocomplete>
  390 + <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>entity-view.entity-view-types</div>
  391 + <tb-entity-subtype-list
  392 + tb-required="true"
  393 + entity-type="types.entityType.entityView"
  394 + ng-model="filter.entityViewTypes">
  395 + </tb-entity-subtype-list>
  396 + </section>
314 397 </div>
... ...
... ... @@ -96,6 +96,8 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q,
96 96 entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true});
97 97 } else if (scope.entityType == types.entityType.device) {
98 98 entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
  99 + } else if (scope.entityType == types.entityType.entityView) {
  100 + entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
99 101 }
100 102 if (entitySubtypesPromise) {
101 103 entitySubtypesPromise.then(
... ...
... ... @@ -97,6 +97,8 @@ export default function EntitySubtypeListDirective($compile, $templateCache, $q,
97 97 entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true});
98 98 } else if (scope.entityType == types.entityType.device) {
99 99 entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
  100 + } else if (scope.entityType == types.entityType.entityView) {
  101 + entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
100 102 }
101 103 if (entitySubtypesPromise) {
102 104 entitySubtypesPromise.then(
... ...
... ... @@ -75,6 +75,8 @@ export default function EntitySubtypeSelect($compile, $templateCache, $translate
75 75 entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true});
76 76 } else if (scope.entityType == types.entityType.device) {
77 77 entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
  78 + } else if (scope.entityType == types.entityType.entityView) {
  79 + entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
78 80 }
79 81 if (entitySubtypesPromise) {
80 82 entitySubtypesPromise.then(
... ...
... ... @@ -158,12 +158,17 @@
158 158 "filter-type-device-type": "Device type",
159 159 "filter-type-device-type-description": "Devices of type '{{deviceType}}'",
160 160 "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'",
  161 + "filter-type-entity-view-type": "Entity View type",
  162 + "filter-type-entity-view-type-description": "Entity Views of type '{{entityView}}'",
  163 + "filter-type-entity-view-type-and-name-description": "Entity Views of type '{{entityView}}' and with name starting with '{{prefix}}'",
161 164 "filter-type-relations-query": "Relations query",
162 165 "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
163 166 "filter-type-asset-search-query": "Asset search query",
164 167 "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
165 168 "filter-type-device-search-query": "Device search query",
166 169 "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
  170 + "filter-type-entity-view-search-query": "Entity view search query",
  171 + "filter-type-entity-view-search-query-description": "Entity views with types {{entityViewTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
167 172 "entity-filter": "Entity filter",
168 173 "resolve-multiple": "Resolve as multiple entities",
169 174 "filter-type": "Filter type",
... ... @@ -839,7 +844,7 @@
839 844 "client-attributes": "Client attributes",
840 845 "shared-attributes": "Shared attributes",
841 846 "server-attributes": "Server attributes",
842   - "latest-timeseries": "Latest timeseries"
  847 + "latest-timeseries": "Latest timeseries",
843 848 },
844 849 "event": {
845 850 "event-type": "Event type",
... ...