Browse Source

Merge with develop/2.1.2

pull/977/merge
Igor Kulikov 8 years ago
parent
commit
8c5d06cdb5
  1. 110
      application/src/main/data/upgrade/2.1.2/schema_update.cql
  2. 32
      application/src/main/data/upgrade/2.1.2/schema_update.sql
  3. 33
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  4. 5
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  5. 39
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
  6. 9
      application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java
  7. 34
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  8. 4
      application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java
  9. 6
      application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
  10. 1
      common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
  11. 1
      common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java
  12. 69
      dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java
  13. 32
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java
  14. 7
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
  15. 54
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  16. 7
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  17. 7
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java
  18. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java
  19. 41
      dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java
  20. 42
      dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java
  21. 49
      dao/src/main/resources/cassandra/schema-entities.cql
  22. 3
      dao/src/main/resources/sql/schema-entities.sql
  23. 2
      dao/src/test/resources/sql/drop-all-tables.sql
  24. 33
      ui/src/app/api/entity.service.js
  25. 8
      ui/src/app/common/types.constant.js
  26. 22
      ui/src/app/entity-view/entity-view-fieldset.tpl.html
  27. 22
      ui/src/app/entity/entity-filter-view.directive.js
  28. 8
      ui/src/app/entity/entity-filter.directive.js
  29. 83
      ui/src/app/entity/entity-filter.tpl.html
  30. 11
      ui/src/app/entity/entity-subtype-autocomplete.directive.js
  31. 4
      ui/src/app/entity/entity-subtype-list.directive.js
  32. 4
      ui/src/app/entity/entity-subtype-select.directive.js
  33. 1
      ui/src/app/help/help-links.constant.js
  34. 8
      ui/src/app/locale/locale.constant-en_US.json
  35. 2104
      ui/src/app/locale/locale.constant-es_ES.json
  36. 2
      ui/src/app/widget/lib/google-map.js

110
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);

32
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
);

33
application/src/main/java/org/thingsboard/server/controller/EntityViewController.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; 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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Customer; 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.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.audit.ActionType; 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.common.data.page.TextPageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -161,6 +164,7 @@ public class EntityViewController extends BaseController {
public TextPageData<EntityView> getCustomerEntityViews( public TextPageData<EntityView> getCustomerEntityViews(
@PathVariable("customerId") String strCustomerId, @PathVariable("customerId") String strCustomerId,
@RequestParam int limit, @RequestParam int limit,
@RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset, @RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException { @RequestParam(required = false) String textOffset) throws ThingsboardException {
@ -170,7 +174,11 @@ public class EntityViewController extends BaseController {
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId); checkCustomerId(customerId);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 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) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
@ -181,13 +189,19 @@ public class EntityViewController extends BaseController {
@ResponseBody @ResponseBody
public TextPageData<EntityView> getTenantEntityViews( public TextPageData<EntityView> getTenantEntityViews(
@RequestParam int limit, @RequestParam int limit,
@RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset, @RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException { @RequestParam(required = false) String textOffset) throws ThingsboardException {
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); 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) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
@ -199,6 +213,7 @@ public class EntityViewController extends BaseController {
public List<EntityView> findByQuery(@RequestBody EntityViewSearchQuery query) throws ThingsboardException { public List<EntityView> findByQuery(@RequestBody EntityViewSearchQuery query) throws ThingsboardException {
checkNotNull(query); checkNotNull(query);
checkNotNull(query.getParameters()); checkNotNull(query.getParameters());
checkNotNull(query.getEntityViewTypes());
checkEntityId(query.getParameters().getEntityId()); checkEntityId(query.getParameters().getEntityId());
try { try {
List<EntityView> entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(query).get()); List<EntityView> entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(query).get());
@ -215,4 +230,18 @@ public class EntityViewController extends BaseController {
throw handleException(e); 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);
}
}
} }

5
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -97,6 +97,11 @@ public class ThingsboardInstallService {
databaseUpgradeService.upgradeDatabase("2.0.0"); 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..."); log.info("Updating system data...");
systemDataLoaderService.deleteSystemWidgetBundle("charts"); systemDataLoaderService.deleteSystemWidgetBundle("charts");

39
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.CUSTOMER_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.DASHBOARD; 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.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.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.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.TENANT_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.TITLE; import static org.thingsboard.server.service.install.DatabaseHelper.TITLE;
import static org.thingsboard.server.service.install.DatabaseHelper.TYPE;
@Service @Service
@NoSqlDao @NoSqlDao
@ -213,6 +222,36 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
break; 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: default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
} }

9
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 CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
public static final String DEVICE = "device"; 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 TENANT_ID = "tenant_id";
public static final String ENTITY_TYPE = "entity_type";
public static final String CUSTOMER_ID = "customer_id"; public static final String CUSTOMER_ID = "customer_id";
public static final String SEARCH_TEXT = "search_text"; public static final String SEARCH_TEXT = "search_text";
public static final String ADDITIONAL_INFO = "additional_info"; public static final String ADDITIONAL_INFO = "additional_info";
public static final String ASSET = "asset"; public static final String ASSET = "asset";
public static final String DASHBOARD = "dashboard"; 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 ID = "id";
public static final String TITLE = "title"; 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 ASSIGNED_CUSTOMERS = "assigned_customers";
public static final String CONFIGURATION = "configuration"; public static final String CONFIGURATION = "configuration";

34
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.Connection;
import java.sql.DriverManager; 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.ASSIGNED_CUSTOMERS;
import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION; 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.CUSTOMER_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.DASHBOARD; 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.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.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.TENANT_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.TITLE; import static org.thingsboard.server.service.install.DatabaseHelper.TITLE;
import static org.thingsboard.server.service.install.DatabaseHelper.TYPE;
@Service @Service
@Profile("install") @Profile("install")
@ -115,6 +125,30 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Schema updated."); log.info("Schema updated.");
} }
break; 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: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);

4
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(); str = new Double(row.getDouble(index)).toString();
} else if (type == DataType.cint()) { } else if (type == DataType.cint()) {
str = new Integer(row.getInt(index)).toString(); str = new Integer(row.getInt(index)).toString();
} else if (type == DataType.bigint()) {
str = new Long(row.getLong(index)).toString();
} else if (type == DataType.uuid()) { } else if (type == DataType.uuid()) {
str = row.getUUID(index).toString(); str = row.getUUID(index).toString();
} else if (type == DataType.timeuuid()) { } else if (type == DataType.timeuuid()) {
@ -193,6 +195,8 @@ public class CassandraDbHelper {
boundStatement.setDouble(column, Double.valueOf(value)); boundStatement.setDouble(column, Double.valueOf(value));
} else if (type == DataType.cint()) { } else if (type == DataType.cint()) {
boundStatement.setInt(column, Integer.valueOf(value)); boundStatement.setInt(column, Integer.valueOf(value));
} else if (type == DataType.bigint()) {
boundStatement.setLong(column, Long.valueOf(value));
} else if (type == DataType.uuid()) { } else if (type == DataType.uuid()) {
boundStatement.setUUID(column, UUID.fromString(value)); boundStatement.setUUID(column, UUID.fromString(value));
} else if (type == DataType.timeuuid()) { } else if (type == DataType.timeuuid()) {

6
application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java

@ -144,7 +144,9 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
@Test @Test
public void testSaveEntityViewWithEmptyName() throws Exception { 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(status().isBadRequest())
.andExpect(statusReason(containsString("Entity view name should be specified!"))); .andExpect(statusReason(containsString("Entity view name should be specified!")));
} }
@ -355,6 +357,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
view.setEntityId(testDevice.getId()); view.setEntityId(testDevice.getId());
view.setTenantId(savedTenant.getId()); view.setTenantId(savedTenant.getId());
view.setName("Test entity view"); view.setName("Test entity view");
view.setType("default");
view.setKeys(telemetry); view.setKeys(telemetry);
view.setStartTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") * 10); view.setStartTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") * 10);
view.setEndTimeMs((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.setEntityId(testDevice.getId());
view.setTenantId(savedTenant.getId()); view.setTenantId(savedTenant.getId());
view.setName(name); view.setName(name);
view.setType("default");
view.setKeys(telemetry); view.setKeys(telemetry);
return doPost("/api/entityView", view, EntityView.class); return doPost("/api/entityView", view, EntityView.class);
} }

1
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 TenantId tenantId;
private CustomerId customerId; private CustomerId customerId;
private String name; private String name;
private String type;
private TelemetryEntityView keys; private TelemetryEntityView keys;
private long startTimeMs; private long startTimeMs;
private long endTimeMs; private long endTimeMs;

1
common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java

@ -30,6 +30,7 @@ public class EntityViewSearchQuery {
private RelationsSearchParameters parameters; private RelationsSearchParameters parameters;
private String relationType; private String relationType;
private List<String> entityViewTypes;
public EntityRelationsQuery toEntitySearchQuery() { public EntityRelationsQuery toEntitySearchQuery() {
EntityRelationsQuery query = new EntityRelationsQuery(); EntityRelationsQuery query = new EntityRelationsQuery();

69
dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java

@ -15,8 +15,13 @@
*/ */
package org.thingsboard.server.dao.entityview; 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.Statement;
import com.datastax.driver.core.querybuilder.Select; 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 com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; 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.nosql.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.util.NoSqlDao; import org.thingsboard.server.dao.util.NoSqlDao;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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.eq;
import static com.datastax.driver.core.querybuilder.QueryBuilder.select; 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.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_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_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_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_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_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_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_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; import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_PROPERTY;
/** /**
@ -82,13 +96,25 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
public List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) { public List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) {
log.debug("Try to find entity views by tenantId [{}] and pageLink [{}]", tenantId, pageLink); log.debug("Try to find entity views by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
List<EntityViewEntity> entityViewEntities = 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); Collections.singletonList(eq(TENANT_ID_PROPERTY, tenantId)), pageLink);
log.trace("Found entity views [{}] by tenantId [{}] and pageLink [{}]", log.trace("Found entity views [{}] by tenantId [{}] and pageLink [{}]",
entityViewEntities, tenantId, pageLink); entityViewEntities, tenantId, pageLink);
return DaoUtil.convertDataList(entityViewEntities); return DaoUtil.convertDataList(entityViewEntities);
} }
@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 @Override
public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) { public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) {
Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_NAME).where(); Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_NAME).where();
@ -110,6 +136,19 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
return DaoUtil.convertDataList(entityViewEntities); return DaoUtil.convertDataList(entityViewEntities);
} }
@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 @Override
public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) { public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) {
log.debug("Try to find entity views by tenantId [{}] and entityId [{}]", tenantId, entityId); log.debug("Try to find entity views by tenantId [{}] and entityId [{}]", tenantId, entityId);
@ -118,4 +157,30 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
query.and(eq(ENTITY_ID_COLUMN, entityId)); query.and(eq(ENTITY_ID_COLUMN, entityId));
return findListByStatementAsync(query); 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();
}
}
});
}
} }

32
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 com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device; 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.EntityView;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
@ -47,6 +48,16 @@ public interface EntityViewDao extends Dao<EntityView> {
*/ */
List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink); 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. * Find entity views by tenantId and entity view name.
* *
@ -68,6 +79,27 @@ public interface EntityViewDao extends Dao<EntityView> {
UUID customerId, UUID customerId,
TextPageLink pageLink); 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); 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);
} }

7
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.entityview; package org.thingsboard.server.dao.entityview;
import com.google.common.util.concurrent.ListenableFuture; 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;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
@ -44,8 +45,12 @@ public interface EntityViewService {
TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink); 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> 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<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query);
ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId); ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId);
@ -55,4 +60,6 @@ public interface EntityViewService {
void deleteEntityView(EntityViewId entityViewId); void deleteEntityView(EntityViewId entityViewId);
void deleteEntityViewsByTenantId(TenantId tenantId); void deleteEntityViewsByTenantId(TenantId tenantId);
ListenableFuture<List<EntitySubtype>> findEntityViewTypesByTenantId(TenantId tenantId);
} }

54
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.entityview; 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.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
@ -29,6 +30,8 @@ import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants; 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.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
@ -54,6 +57,8 @@ import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors; 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.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId; 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.validatePageLink;
import static org.thingsboard.server.dao.service.Validator.validateString;
/** /**
* Created by Victor Basanets on 8/28/2017. * Created by Victor Basanets on 8/28/2017.
@ -157,6 +163,16 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
return new TextPageData<>(entityViews, pageLink); return new TextPageData<>(entityViews, pageLink);
} }
@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 @Override
public TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, public TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId,
TextPageLink pageLink) { TextPageLink pageLink) {
@ -170,6 +186,19 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
return new TextPageData<>(entityViews, pageLink); return new TextPageData<>(entityViews, pageLink);
} }
@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 @Override
public ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query) { public ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query) {
ListenableFuture<List<EntityRelation>> relations = relationService.findByQuery(query.toEntitySearchQuery()); ListenableFuture<List<EntityRelation>> relations = relationService.findByQuery(query.toEntitySearchQuery());
@ -184,6 +213,15 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
} }
return Futures.successfulAsList(futures); 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; return entityViews;
} }
@ -216,6 +254,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
public void onSuccess(@Nullable List<EntityView> result) { public void onSuccess(@Nullable List<EntityView> result) {
cache.putIfAbsent(tenantIdAndEntityId, result); cache.putIfAbsent(tenantIdAndEntityId, result);
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("Error while finding entity views by tenantId and entityId", 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); 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) { private ListenableFuture<List<Void>> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection<String> keys) {
if (keys != null && !keys.isEmpty()) { if (keys != null && !keys.isEmpty()) {
ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys); ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys);
@ -296,6 +347,9 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
@Override @Override
protected void validateDataImpl(EntityView entityView) { protected void validateDataImpl(EntityView entityView) {
if (StringUtils.isEmpty(entityView.getType())) {
throw new DataValidationException("Entity View type should be specified!");
}
if (StringUtils.isEmpty(entityView.getName())) { if (StringUtils.isEmpty(entityView.getName())) {
throw new DataValidationException("Entity view name should be specified!"); throw new DataValidationException("Entity view name should be specified!");
} }

7
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -145,18 +145,21 @@ public class ModelConstants {
/** /**
* Cassandra entityView constants. * 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_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN;
public static final String ENTITY_VIEW_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; 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_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String ENTITY_VIEW_NAME_PROPERTY = DEVICE_NAME_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_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_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_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_START_TS_PROPERTY = "start_ts";
public static final String ENTITY_VIEW_END_TS_PROPERTY = "end_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_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"; public static final String ENTITY_VIEW_BY_TENANT_AND_NAME = "entity_view_by_tenant_and_name";
/** /**

7
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.io.IOException;
import java.util.UUID; 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_TYPE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME; import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; 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) @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY)
private UUID customerId; private UUID customerId;
@PartitionKey(value = 3)
@Column(name = DEVICE_TYPE_PROPERTY)
private String type;
@Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY) @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY)
private UUID entityId; private UUID entityId;
@ -113,6 +118,7 @@ public class EntityViewEntity implements SearchTextEntity<EntityView> {
if (entityView.getCustomerId() != null) { if (entityView.getCustomerId() != null) {
this.customerId = entityView.getCustomerId().getId(); this.customerId = entityView.getCustomerId().getId();
} }
this.type = entityView.getType();
this.name = entityView.getName(); this.name = entityView.getName();
try { try {
this.keys = mapper.writeValueAsString(entityView.getKeys()); this.keys = mapper.writeValueAsString(entityView.getKeys());
@ -143,6 +149,7 @@ public class EntityViewEntity implements SearchTextEntity<EntityView> {
if (customerId != null) { if (customerId != null) {
entityView.setCustomerId(new CustomerId(customerId)); entityView.setCustomerId(new CustomerId(customerId));
} }
entityView.setType(type);
entityView.setName(name); entityView.setName(name);
try { try {
entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class)); entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class));

5
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) @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY)
private String customerId; private String customerId;
@Column(name = ModelConstants.DEVICE_TYPE_PROPERTY)
private String type;
@Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY) @Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY)
private String name; private String name;
@ -108,6 +111,7 @@ public class EntityViewEntity extends BaseSqlEntity<EntityView> implements Searc
if (entityView.getCustomerId() != null) { if (entityView.getCustomerId() != null) {
this.customerId = toString(entityView.getCustomerId().getId()); this.customerId = toString(entityView.getCustomerId().getId());
} }
this.type = entityView.getType();
this.name = entityView.getName(); this.name = entityView.getName();
try { try {
this.keys = mapper.writeValueAsString(entityView.getKeys()); this.keys = mapper.writeValueAsString(entityView.getKeys());
@ -144,6 +148,7 @@ public class EntityViewEntity extends BaseSqlEntity<EntityView> implements Searc
if (customerId != null) { if (customerId != null) {
entityView.setCustomerId(new CustomerId(toUUID(customerId))); entityView.setCustomerId(new CustomerId(toUUID(customerId)));
} }
entityView.setType(type);
entityView.setName(name); entityView.setName(name);
try { try {
entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class)); entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class));

41
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.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param; 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.model.sql.EntityViewEntity;
import org.thingsboard.server.dao.util.SqlDao; 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 LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " +
"AND e.id > :idOffset ORDER BY e.id") "AND e.id > :idOffset ORDER BY e.id")
List<EntityViewEntity> findByTenantId(@Param("tenantId") String tenantId, List<EntityViewEntity> findByTenantId(@Param("tenantId") String tenantId,
@Param("textSearch") String textSearch, @Param("textSearch") String textSearch,
@Param("idOffset") String idOffset, @Param("idOffset") String idOffset,
Pageable pageable); 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 " + @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " +
"AND e.customerId = :customerId " + "AND e.customerId = :customerId " +
"AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
"AND e.id > :idOffset ORDER BY e.id") "AND e.id > :idOffset ORDER BY e.id")
List<EntityViewEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId, List<EntityViewEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
@Param("customerId") String customerId, @Param("customerId") String customerId,
@Param("searchText") String searchText, @Param("searchText") String searchText,
@Param("idOffset") String idOffset, @Param("idOffset") String idOffset,
Pageable pageable); 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); EntityViewEntity findByTenantIdAndName(String tenantId, String name);
List<EntityViewEntity> findAllByTenantIdAndEntityId(String tenantId, String entityId); 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);
} }

42
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.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.UUIDConverter; 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.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
@ -41,7 +40,6 @@ import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; 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; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
/** /**
@ -75,6 +73,17 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao<EntityViewEntity,
new PageRequest(0, pageLink.getLimit()))); new PageRequest(0, pageLink.getLimit())));
} }
@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 @Override
public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) { public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) {
return Optional.ofNullable( return Optional.ofNullable(
@ -95,9 +104,38 @@ 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 @Override
public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) { public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) {
return service.submit(() -> DaoUtil.convertDataList( return service.submit(() -> DaoUtil.convertDataList(
entityViewRepository.findAllByTenantIdAndEntityId(UUIDConverter.fromTimeUUID(tenantId), UUIDConverter.fromTimeUUID(entityId)))); 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;
}
} }

49
dao/src/main/resources/cassandra/schema-entities.cql

@ -624,61 +624,90 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
PRIMARY KEY (id) PRIMARY KEY (id)
); );
CREATE TABLE IF NOT EXISTS thingsboard.entity_views ( CREATE TABLE IF NOT EXISTS thingsboard.entity_view (
id timeuuid, id timeuuid,
entity_id timeuuid, entity_id timeuuid,
entity_type text, entity_type text,
tenant_id timeuuid, tenant_id timeuuid,
customer_id timeuuid, customer_id timeuuid,
name text, name text,
type text,
keys text, keys text,
start_ts bigint, start_ts bigint,
end_ts bigint, end_ts bigint,
search_text text, search_text text,
additional_info 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 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS
SELECT * SELECT *
from thingsboard.entity_views from thingsboard.entity_view
WHERE tenant_id IS NOT NULL WHERE tenant_id IS NOT NULL
AND entity_id IS NOT NULL AND entity_id IS NOT NULL
AND customer_id IS NOT NULL AND customer_id IS NOT NULL
AND type IS NOT NULL
AND name IS NOT NULL AND name IS NOT NULL
AND id 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); 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 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS
SELECT * SELECT *
from thingsboard.entity_views from thingsboard.entity_view
WHERE tenant_id IS NOT NULL WHERE tenant_id IS NOT NULL
AND entity_id IS NOT NULL AND entity_id IS NOT NULL
AND customer_id IS NOT NULL AND customer_id IS NOT NULL
AND type IS NOT NULL
AND search_text IS NOT NULL AND search_text IS NOT NULL
AND id 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); 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 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS
SELECT * SELECT *
from thingsboard.entity_views from thingsboard.entity_view
WHERE tenant_id IS NOT NULL WHERE tenant_id IS NOT NULL
AND customer_id IS NOT NULL AND customer_id IS NOT NULL
AND entity_id IS NOT NULL AND entity_id IS NOT NULL
AND type IS NOT NULL
AND search_text IS NOT NULL AND search_text IS NOT NULL
AND id 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); 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 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS
SELECT * SELECT *
from thingsboard.entity_views from thingsboard.entity_view
WHERE tenant_id IS NOT NULL WHERE tenant_id IS NOT NULL
AND customer_id IS NOT NULL AND customer_id IS NOT NULL
AND entity_id IS NOT NULL AND entity_id IS NOT NULL
AND type IS NOT NULL
AND search_text IS NOT NULL AND search_text IS NOT NULL
AND id 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); WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);

3
dao/src/main/resources/sql/schema-entities.sql

@ -228,12 +228,13 @@ CREATE TABLE IF NOT EXISTS rule_node (
search_text varchar(255) 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, id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
entity_id varchar(31), entity_id varchar(31),
entity_type varchar(255), entity_type varchar(255),
tenant_id varchar(31), tenant_id varchar(31),
customer_id varchar(31), customer_id varchar(31),
type varchar(255),
name varchar(255), name varchar(255),
keys varchar(255), keys varchar(255),
start_ts bigint, start_ts bigint,

2
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 widgets_bundle;
DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_node;
DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS rule_chain;
DROP TABLE IF EXISTS entity_views; DROP TABLE IF EXISTS entity_view;

33
ui/src/app/api/entity.service.js

@ -533,6 +533,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
} }
); );
break; 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: case types.aliasFilterType.relationsQuery.value:
result.stateEntity = filter.rootStateEntity; result.stateEntity = filter.rootStateEntity;
var rootEntityType; var rootEntityType;
@ -578,6 +593,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
break; break;
case types.aliasFilterType.assetSearchQuery.value: case types.aliasFilterType.assetSearchQuery.value:
case types.aliasFilterType.deviceSearchQuery.value: case types.aliasFilterType.deviceSearchQuery.value:
case types.aliasFilterType.entityViewSearchQuery.value:
result.stateEntity = filter.rootStateEntity; result.stateEntity = filter.rootStateEntity;
if (result.stateEntity && stateEntityId) { if (result.stateEntity && stateEntityId) {
rootEntityType = stateEntityId.entityType; rootEntityType = stateEntityId.entityType;
@ -604,6 +620,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
} else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) { } else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) {
searchQuery.deviceTypes = filter.deviceTypes; searchQuery.deviceTypes = filter.deviceTypes;
findByQueryPromise = deviceService.findByQuery(searchQuery, false, {ignoreLoading: true}); 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( findByQueryPromise.then(
function success(entities) { 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; return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
case types.aliasFilterType.deviceType.value: case types.aliasFilterType.deviceType.value:
return entityTypes.indexOf(types.entityType.device) > -1 ? true : false; 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: case types.aliasFilterType.relationsQuery.value:
if (filter.filters && filter.filters.length) { if (filter.filters && filter.filters.length) {
var match = false; 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; return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
case types.aliasFilterType.deviceSearchQuery.value: case types.aliasFilterType.deviceSearchQuery.value:
return entityTypes.indexOf(types.entityType.device) > -1 ? true : false; 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; return false;
@ -690,12 +713,16 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return entityType === types.entityType.asset; return entityType === types.entityType.asset;
case types.aliasFilterType.deviceType.value: case types.aliasFilterType.deviceType.value:
return entityType === types.entityType.device; return entityType === types.entityType.device;
case types.aliasFilterType.entityViewType.value:
return entityType === types.entityType.entityView;
case types.aliasFilterType.relationsQuery.value: case types.aliasFilterType.relationsQuery.value:
return true; return true;
case types.aliasFilterType.assetSearchQuery.value: case types.aliasFilterType.assetSearchQuery.value:
return entityType === types.entityType.asset; return entityType === types.entityType.asset;
case types.aliasFilterType.deviceSearchQuery.value: case types.aliasFilterType.deviceSearchQuery.value:
return entityType === types.entityType.device; return entityType === types.entityType.device;
case types.aliasFilterType.entityViewSearchQuery.value:
return entityType === types.entityType.entityView;
} }
return false; return false;
} }
@ -1046,6 +1073,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return assetService.deleteAsset(entityId.id); return assetService.deleteAsset(entityId.id);
} else if (entityId.entityType == types.entityType.device) { } else if (entityId.entityType == types.entityType.device) {
return deviceService.deleteDevice(entityId.id); 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); return assetService.saveAsset(entity);
} else if (entityType == types.entityType.device) { } else if (entityType == types.entityType.device) {
return deviceService.saveDevice(entity); 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; searchQuery.assetTypes = entitySubTypes;
} else if (entityType == types.entityType.device) { } else if (entityType == types.entityType.device) {
searchQuery.deviceTypes = entitySubTypes; searchQuery.deviceTypes = entitySubTypes;
} else if (entityType == types.entityType.entityView) {
searchQuery.entityViewTypes = entitySubTypes;
} else { } else {
return null; //Not supported return null; //Not supported
} }

8
ui/src/app/common/types.constant.js

@ -253,6 +253,10 @@ export default angular.module('thingsboard.types', [])
value: 'deviceType', value: 'deviceType',
name: 'alias.filter-type-device-type' name: 'alias.filter-type-device-type'
}, },
entityViewType: {
value: 'entityViewType',
name: 'alias.filter-type-entity-view-type'
},
relationsQuery: { relationsQuery: {
value: 'relationsQuery', value: 'relationsQuery',
name: 'alias.filter-type-relations-query' name: 'alias.filter-type-relations-query'
@ -264,6 +268,10 @@ export default angular.module('thingsboard.types', [])
deviceSearchQuery: { deviceSearchQuery: {
value: 'deviceSearchQuery', value: 'deviceSearchQuery',
name: 'alias.filter-type-device-search-query' name: 'alias.filter-type-device-search-query'
},
entityViewSearchQuery: {
value: 'entityViewSearchQuery',
name: 'alias.filter-type-entity-view-search-query'
} }
}, },
position: { position: {

22
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 translate ng-message="required">entity-view.name-required</div>
</div> </div>
</md-input-container> </md-input-container>
<tb-entity-select flex ng-disabled="!isEdit" <tb-entity-subtype-autocomplete
the-form="theForm" ng-disabled="$root.loading || !isEdit"
tb-required="true" tb-required="true"
allowed-entity-types="allowedEntityTypes" the-form="theForm"
ng-model="entityView.entityId"> ng-model="entityView.type"
</tb-entity-select> 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"> <md-input-container class="md-block">
<label translate>entity-view.description</label> <label translate>entity-view.description</label>
<textarea ng-model="entityView.additionalInfo.description" rows="2"></textarea> <textarea ng-model="entityView.additionalInfo.description" rows="2"></textarea>

22
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}); scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType});
} }
break; 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: case types.aliasFilterType.relationsQuery.value:
var rootEntityText; var rootEntityText;
var directionText; var directionText;
@ -134,6 +143,7 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q,
break; break;
case types.aliasFilterType.assetSearchQuery.value: case types.aliasFilterType.assetSearchQuery.value:
case types.aliasFilterType.deviceSearchQuery.value: case types.aliasFilterType.deviceSearchQuery.value:
case types.aliasFilterType.entityViewSearchQuery.value:
allEntitiesText = $translate.instant('alias.all-entities'); allEntitiesText = $translate.instant('alias.all-entities');
anyRelationText = $translate.instant('alias.any-relation'); anyRelationText = $translate.instant('alias.any-relation');
if (scope.filter.rootStateEntity) { 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', scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-search-query-description',
translationValues translationValues
); );
} else { } else if (scope.filter.type == types.aliasFilterType.deviceSearchQuery.value) {
var deviceTypesQuoted = []; var deviceTypesQuoted = [];
scope.filter.deviceTypes.forEach(function(deviceType) { scope.filter.deviceTypes.forEach(function(deviceType) {
deviceTypesQuoted.push("'"+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', scope.filterDisplayValue = $translate.instant('alias.filter-type-device-search-query-description',
translationValues 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; break;
default: default:

8
ui/src/app/entity/entity-filter.directive.js

@ -69,9 +69,14 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
filter.deviceType = null; filter.deviceType = null;
filter.deviceNameFilter = ''; filter.deviceNameFilter = '';
break; break;
case types.aliasFilterType.entityViewType.value:
filter.entityViewType = null;
filter.entityViewNameFilter = '';
break;
case types.aliasFilterType.relationsQuery.value: case types.aliasFilterType.relationsQuery.value:
case types.aliasFilterType.assetSearchQuery.value: case types.aliasFilterType.assetSearchQuery.value:
case types.aliasFilterType.deviceSearchQuery.value: case types.aliasFilterType.deviceSearchQuery.value:
case types.aliasFilterType.entityViewSearchQuery.value:
filter.rootStateEntity = false; filter.rootStateEntity = false;
filter.stateEntityParamName = null; filter.stateEntityParamName = null;
filter.defaultStateEntity = null; filter.defaultStateEntity = null;
@ -86,6 +91,9 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
} else if (filter.type === types.aliasFilterType.deviceSearchQuery.value) { } else if (filter.type === types.aliasFilterType.deviceSearchQuery.value) {
filter.relationType = null; filter.relationType = null;
filter.deviceTypes = []; filter.deviceTypes = [];
} else if (filter.type === types.aliasFilterType.entityViewSearchQuery.value) {
filter.relationType = null;
filter.entityViewTypes = [];
} }
break; break;
} }

83
ui/src/app/entity/entity-filter.tpl.html

@ -112,6 +112,20 @@
aria-label="{{ 'device.name-starts-with' | translate }}"> aria-label="{{ 'device.name-starts-with' | translate }}">
</md-input-container> </md-input-container>
</section> </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"> <section layout="column" ng-if="filter.type == types.aliasFilterType.relationsQuery.value" id="relationsQueryFilter">
<label class="tb-small">{{ 'alias.root-entity' | translate }}</label> <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;"> <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"> ng-model="filter.deviceTypes">
</tb-entity-subtype-list> </tb-entity-subtype-list>
</section> </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> </div>

11
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 */ /* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/ /*@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 linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entitySubtypeAutocompleteTemplate); var template = $templateCache.get(entitySubtypeAutocompleteTemplate);
@ -96,6 +96,8 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q,
entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true}); entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true});
} else if (scope.entityType == types.entityType.device) { } else if (scope.entityType == types.entityType.device) {
entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true}); entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
} else if (scope.entityType == types.entityType.entityView) {
entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
} }
if (entitySubtypesPromise) { if (entitySubtypesPromise) {
entitySubtypesPromise.then( entitySubtypesPromise.then(
@ -134,6 +136,13 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q,
scope.$on('deviceSaved', function() { scope.$on('deviceSaved', function() {
scope.entitySubtypes = null; 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;
});
} }
} }

4
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'; import './entity-subtype-list.scss';
/*@ngInject*/ /*@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) { var linker = function (scope, element, attrs, ngModelCtrl) {
@ -97,6 +97,8 @@ export default function EntitySubtypeListDirective($compile, $templateCache, $q,
entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true}); entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true});
} else if (scope.entityType == types.entityType.device) { } else if (scope.entityType == types.entityType.device) {
entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true}); entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
} else if (scope.entityType == types.entityType.entityView) {
entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
} }
if (entitySubtypesPromise) { if (entitySubtypesPromise) {
entitySubtypesPromise.then( entitySubtypesPromise.then(

4
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 */ /* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/ /*@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 linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entitySubtypeSelectTemplate); var template = $templateCache.get(entitySubtypeSelectTemplate);
@ -75,6 +75,8 @@ export default function EntitySubtypeSelect($compile, $templateCache, $translate
entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true}); entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true});
} else if (scope.entityType == types.entityType.device) { } else if (scope.entityType == types.entityType.device) {
entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true}); entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
} else if (scope.entityType == types.entityType.entityView) {
entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
} }
if (entitySubtypesPromise) { if (entitySubtypesPromise) {
entitySubtypesPromise.then( entitySubtypesPromise.then(

1
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", customers: helpBaseUrl + "/docs/user-guide/ui/customers",
assets: helpBaseUrl + "/docs/user-guide/ui/assets", assets: helpBaseUrl + "/docs/user-guide/ui/assets",
devices: helpBaseUrl + "/docs/user-guide/ui/devices", devices: helpBaseUrl + "/docs/user-guide/ui/devices",
entityViews: helpBaseUrl + "/docs/user-guide/ui/entity-views",
dashboards: helpBaseUrl + "/docs/user-guide/ui/dashboards", dashboards: helpBaseUrl + "/docs/user-guide/ui/dashboards",
users: helpBaseUrl + "/docs/user-guide/ui/users", users: helpBaseUrl + "/docs/user-guide/ui/users",
widgetsBundles: helpBaseUrl + "/docs/user-guide/ui/widget-library#bundles", widgetsBundles: helpBaseUrl + "/docs/user-guide/ui/widget-library#bundles",

8
ui/src/app/locale/locale.constant-en_US.json

@ -158,12 +158,17 @@
"filter-type-device-type": "Device type", "filter-type-device-type": "Device type",
"filter-type-device-type-description": "Devices of type '{{deviceType}}'", "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-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": "Relations query",
"filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "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": "Asset search query",
"filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "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": "Device search query",
"filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "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", "entity-filter": "Entity filter",
"resolve-multiple": "Resolve as multiple entities", "resolve-multiple": "Resolve as multiple entities",
"filter-type": "Filter type", "filter-type": "Filter type",
@ -839,7 +844,8 @@
"client-attributes": "Client attributes", "client-attributes": "Client attributes",
"shared-attributes": "Shared attributes", "shared-attributes": "Shared attributes",
"server-attributes": "Server attributes", "server-attributes": "Server attributes",
"latest-timeseries": "Latest timeseries" "latest-timeseries": "Latest timeseries",
"related-entity": "Related entity"
}, },
"event": { "event": {
"event-type": "Event type", "event-type": "Event type",

2104
ui/src/app/locale/locale.constant-es_ES.json

File diff suppressed because it is too large

2
ui/src/app/widget/lib/google-map.js

@ -44,7 +44,7 @@ export default class TbGoogleMap {
function initGoogleMap() { function initGoogleMap() {
tbMap.map = new google.maps.Map($containerElement[0], { // eslint-disable-line no-undef tbMap.map = new google.maps.Map($containerElement[0], { // eslint-disable-line no-undef
scrollwheel: false, scrollwheel: true,
mapTypeId: getGoogleMapTypeId(tbMap.defaultMapType), mapTypeId: getGoogleMapTypeId(tbMap.defaultMapType),
zoom: tbMap.defaultZoomLevel || 8 zoom: tbMap.defaultZoomLevel || 8
}); });

Loading…
Cancel
Save