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