Commit 063a1093e0179ae7840d0f8e3bce3afdeeb08dad

Authored by Igor Kulikov
1 parent b90f0ea0

Ability to assign dashboards to multiple customers - DAO Layer.

Showing 32 changed files with 830 additions and 420 deletions
... ... @@ -87,3 +87,26 @@ CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions (
87 87 PRIMARY KEY (( tenant_id ), partition)
88 88 ) WITH CLUSTERING ORDER BY ( partition ASC )
89 89 AND compaction = { 'class' : 'LeveledCompactionStrategy' };
  90 +
  91 +DROP MATERIALIZED VIEW IF EXISTS thingsboard.dashboard_by_tenant_and_search_text;
  92 +DROP MATERIALIZED VIEW IF EXISTS thingsboard.dashboard_by_customer_and_search_text;
  93 +
  94 +DROP TABLE IF EXISTS thingsboard.dashboard;
  95 +
  96 +CREATE TABLE IF NOT EXISTS thingsboard.dashboard (
  97 + id timeuuid,
  98 + tenant_id timeuuid,
  99 + title text,
  100 + search_text text,
  101 + assigned_customers text,
  102 + configuration text,
  103 + PRIMARY KEY (id, tenant_id)
  104 +);
  105 +
  106 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
  107 + SELECT *
  108 + from thingsboard.dashboard
  109 + WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
  110 + PRIMARY KEY ( tenant_id, search_text, id )
  111 + WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
  112 +
... ...
... ... @@ -29,3 +29,13 @@ CREATE TABLE IF NOT EXISTS audit_log (
29 29 action_failure_details varchar(1000000)
30 30 );
31 31
  32 +DROP TABLE IF EXISTS dashboard;
  33 +
  34 +CREATE TABLE IF NOT EXISTS dashboard (
  35 + id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
  36 + configuration varchar(10000000),
  37 + assigned_customers varchar(1000000),
  38 + search_text varchar(255),
  39 + tenant_id varchar(31),
  40 + title varchar(255)
  41 +);
... ...
... ... @@ -423,7 +423,7 @@ public abstract class BaseController {
423 423 try {
424 424 validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
425 425 Dashboard dashboard = dashboardService.findDashboardById(dashboardId);
426   - checkDashboard(dashboard, true);
  426 + checkDashboard(dashboard);
427 427 return dashboard;
428 428 } catch (Exception e) {
429 429 throw handleException(e, false);
... ... @@ -435,27 +435,23 @@ public abstract class BaseController {
435 435 validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
436 436 DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId);
437 437 SecurityUser authUser = getCurrentUser();
438   - checkDashboard(dashboardInfo, authUser.getAuthority() != Authority.SYS_ADMIN);
  438 + checkDashboard(dashboardInfo);
439 439 return dashboardInfo;
440 440 } catch (Exception e) {
441 441 throw handleException(e, false);
442 442 }
443 443 }
444 444
445   - private void checkDashboard(DashboardInfo dashboard, boolean checkCustomerId) throws ThingsboardException {
  445 + private void checkDashboard(DashboardInfo dashboard) throws ThingsboardException {
446 446 checkNotNull(dashboard);
447 447 checkTenantId(dashboard.getTenantId());
448 448 SecurityUser authUser = getCurrentUser();
449 449 if (authUser.getAuthority() == Authority.CUSTOMER_USER) {
450   - if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
  450 + if (dashboard.getAssignedCustomers() == null || !dashboard.getAssignedCustomers().containsKey(authUser.getCustomerId().toString())) {
451 451 throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
452 452 ThingsboardErrorCode.PERMISSION_DENIED);
453 453 }
454 454 }
455   - if (checkCustomerId &&
456   - dashboard.getCustomerId() != null && !dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
457   - checkCustomerId(dashboard.getCustomerId());
458   - }
459 455 }
460 456
461 457 ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException {
... ...
... ... @@ -28,6 +28,8 @@ import org.thingsboard.server.common.data.id.DashboardId;
28 28 import org.thingsboard.server.common.data.id.TenantId;
29 29 import org.thingsboard.server.common.data.page.TextPageData;
30 30 import org.thingsboard.server.common.data.page.TextPageLink;
  31 +import org.thingsboard.server.common.data.page.TimePageData;
  32 +import org.thingsboard.server.common.data.page.TimePageLink;
31 33 import org.thingsboard.server.dao.exception.IncorrectParameterException;
32 34 import org.thingsboard.server.dao.model.ModelConstants;
33 35 import org.thingsboard.server.exception.ThingsboardException;
... ... @@ -80,7 +82,7 @@ public class DashboardController extends BaseController {
80 82 Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard));
81 83
82 84 logEntityAction(savedDashboard.getId(), savedDashboard,
83   - savedDashboard.getCustomerId(),
  85 + null,
84 86 dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
85 87
86 88 return savedDashboard;
... ... @@ -103,7 +105,7 @@ public class DashboardController extends BaseController {
103 105 dashboardService.deleteDashboard(dashboardId);
104 106
105 107 logEntityAction(dashboardId, dashboard,
106   - dashboard.getCustomerId(),
  108 + null,
107 109 ActionType.DELETED, null, strDashboardId);
108 110
109 111 } catch (Exception e) {
... ... @@ -134,7 +136,7 @@ public class DashboardController extends BaseController {
134 136 Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
135 137
136 138 logEntityAction(dashboardId, savedDashboard,
137   - savedDashboard.getCustomerId(),
  139 + customerId,
138 140 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName());
139 141
140 142
... ... @@ -150,23 +152,22 @@ public class DashboardController extends BaseController {
150 152 }
151 153
152 154 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
153   - @RequestMapping(value = "/customer/dashboard/{dashboardId}", method = RequestMethod.DELETE)
  155 + @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
154 156 @ResponseBody
155   - public Dashboard unassignDashboardFromCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
  157 + public Dashboard unassignDashboardFromCustomer(@PathVariable("customerId") String strCustomerId,
  158 + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
  159 + checkParameter("customerId", strCustomerId);
156 160 checkParameter(DASHBOARD_ID, strDashboardId);
157 161 try {
  162 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  163 + Customer customer = checkCustomerId(customerId);
158 164 DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
159 165 Dashboard dashboard = checkDashboardId(dashboardId);
160   - if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
161   - throw new IncorrectParameterException("Dashboard isn't assigned to any customer!");
162   - }
163   -
164   - Customer customer = checkCustomerId(dashboard.getCustomerId());
165 166
166   - Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId));
  167 + Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
167 168
168 169 logEntityAction(dashboardId, dashboard,
169   - dashboard.getCustomerId(),
  170 + customerId,
170 171 ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName());
171 172
172 173 return savedDashboard;
... ... @@ -192,7 +193,7 @@ public class DashboardController extends BaseController {
192 193 Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId()));
193 194
194 195 logEntityAction(dashboardId, savedDashboard,
195   - savedDashboard.getCustomerId(),
  196 + publicCustomer.getId(),
196 197 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
197 198
198 199 return savedDashboard;
... ... @@ -206,6 +207,33 @@ public class DashboardController extends BaseController {
206 207 }
207 208 }
208 209
  210 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  211 + @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE)
  212 + @ResponseBody
  213 + public Dashboard unassignDashboardFromPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
  214 + checkParameter(DASHBOARD_ID, strDashboardId);
  215 + try {
  216 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  217 + Dashboard dashboard = checkDashboardId(dashboardId);
  218 + Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId());
  219 +
  220 + Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, publicCustomer.getId()));
  221 +
  222 + logEntityAction(dashboardId, dashboard,
  223 + publicCustomer.getId(),
  224 + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
  225 +
  226 + return savedDashboard;
  227 + } catch (Exception e) {
  228 +
  229 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  230 + null,
  231 + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
  232 +
  233 + throw handleException(e);
  234 + }
  235 + }
  236 +
209 237 @PreAuthorize("hasAuthority('SYS_ADMIN')")
210 238 @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
211 239 @ResponseBody
... ... @@ -245,19 +273,20 @@ public class DashboardController extends BaseController {
245 273 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
246 274 @RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
247 275 @ResponseBody
248   - public TextPageData<DashboardInfo> getCustomerDashboards(
  276 + public TimePageData<DashboardInfo> getCustomerDashboards(
249 277 @PathVariable("customerId") String strCustomerId,
250 278 @RequestParam int limit,
251   - @RequestParam(required = false) String textSearch,
252   - @RequestParam(required = false) String idOffset,
253   - @RequestParam(required = false) String textOffset) throws ThingsboardException {
  279 + @RequestParam(required = false) Long startTime,
  280 + @RequestParam(required = false) Long endTime,
  281 + @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
  282 + @RequestParam(required = false) String offset) throws ThingsboardException {
254 283 checkParameter("customerId", strCustomerId);
255 284 try {
256 285 TenantId tenantId = getCurrentUser().getTenantId();
257 286 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
258 287 checkCustomerId(customerId);
259   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
260   - return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
  288 + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
  289 + return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get());
261 290 } catch (Exception e) {
262 291 throw handleException(e);
263 292 }
... ...
... ... @@ -24,6 +24,7 @@ import org.springframework.context.annotation.Profile;
24 24 import org.springframework.stereotype.Service;
25 25 import org.thingsboard.server.dao.cassandra.CassandraCluster;
26 26 import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
  27 +import org.thingsboard.server.dao.dashboard.DashboardService;
27 28 import org.thingsboard.server.dao.util.NoSqlDao;
28 29 import org.thingsboard.server.service.install.cql.CQLStatementsParser;
29 30 import org.thingsboard.server.service.install.cql.CassandraDbHelper;
... ... @@ -33,6 +34,8 @@ import java.nio.file.Path;
33 34 import java.nio.file.Paths;
34 35 import java.util.List;
35 36
  37 +import static org.thingsboard.server.service.install.DatabaseHelper.*;
  38 +
36 39 @Service
37 40 @NoSqlDao
38 41 @Profile("install")
... ... @@ -40,12 +43,6 @@ import java.util.List;
40 43 public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
41 44
42 45 private static final String SCHEMA_UPDATE_CQL = "schema_update.cql";
43   - public static final String DEVICE = "device";
44   - public static final String TENANT_ID = "tenant_id";
45   - public static final String CUSTOMER_ID = "customer_id";
46   - public static final String SEARCH_TEXT = "search_text";
47   - public static final String ADDITIONAL_INFO = "additional_info";
48   - public static final String ASSET = "asset";
49 46
50 47 @Value("${install.data_dir}")
51 48 private String dataDir;
... ... @@ -56,6 +53,9 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
56 53 @Autowired
57 54 private CassandraInstallCluster installCluster;
58 55
  56 + @Autowired
  57 + private DashboardService dashboardService;
  58 +
59 59 @Override
60 60 public void upgradeDatabase(String fromVersion) throws Exception {
61 61
... ... @@ -160,10 +160,32 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
160 160 case "1.3.0":
161 161 break;
162 162 case "1.3.1":
  163 +
  164 + cluster.getSession();
  165 +
  166 + ks = cluster.getCluster().getMetadata().getKeyspace(cluster.getKeyspaceName());
  167 +
  168 + log.info("Dumping dashboards ...");
  169 + Path dashboardsDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), DASHBOARD,
  170 + new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION},
  171 + new String[]{"", "", "", "", "", "", ""},
  172 + "tb-dashboards");
  173 + log.info("Dashboards dumped.");
  174 +
  175 +
163 176 log.info("Updating schema ...");
164 177 schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
165 178 loadCql(schemaUpdateFile);
166 179 log.info("Schema updated.");
  180 +
  181 + log.info("Restoring dashboards ...");
  182 + if (dashboardsDump != null) {
  183 + CassandraDbHelper.loadCf(ks, cluster.getSession(), DASHBOARD,
  184 + new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump);
  185 + DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, false);
  186 + Files.deleteIfExists(dashboardsDump);
  187 + }
  188 + log.info("Dashboards restored.");
167 189 break;
168 190 default:
169 191 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
... ...
  1 +/**
  2 + * Copyright © 2016-2017 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 +package org.thingsboard.server.service.install;
  17 +
  18 +import com.fasterxml.jackson.databind.ObjectMapper;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.apache.commons.csv.CSVFormat;
  21 +import org.apache.commons.csv.CSVParser;
  22 +import org.apache.commons.lang3.StringUtils;
  23 +import com.fasterxml.jackson.databind.JsonNode;
  24 +import org.thingsboard.server.common.data.UUIDConverter;
  25 +import org.thingsboard.server.common.data.id.CustomerId;
  26 +import org.thingsboard.server.common.data.id.DashboardId;
  27 +import org.thingsboard.server.dao.dashboard.DashboardService;
  28 +
  29 +import java.io.IOException;
  30 +import java.nio.file.Files;
  31 +import java.nio.file.Path;
  32 +import java.util.*;
  33 +
  34 +/**
  35 + * Created by igor on 2/27/18.
  36 + */
  37 +@Slf4j
  38 +public class DatabaseHelper {
  39 +
  40 + public static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
  41 +
  42 + public static final String DEVICE = "device";
  43 + public static final String TENANT_ID = "tenant_id";
  44 + public static final String CUSTOMER_ID = "customer_id";
  45 + public static final String SEARCH_TEXT = "search_text";
  46 + public static final String ADDITIONAL_INFO = "additional_info";
  47 + public static final String ASSET = "asset";
  48 + public static final String DASHBOARD = "dashboard";
  49 + public static final String ID = "id";
  50 + public static final String TITLE = "title";
  51 + public static final String ASSIGNED_CUSTOMERS = "assigned_customers";
  52 + public static final String CONFIGURATION = "configuration";
  53 +
  54 + public static final ObjectMapper objectMapper = new ObjectMapper();
  55 +
  56 + public static void upgradeTo40_assignDashboards(Path dashboardsDump, DashboardService dashboardService, boolean sql) throws Exception {
  57 + String[] columns = new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION};
  58 + try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(dashboardsDump), CSV_DUMP_FORMAT.withHeader(columns))) {
  59 + csvParser.forEach(record -> {
  60 + String customerIdString = record.get(CUSTOMER_ID);
  61 + String assignedCustomersString = record.get(ASSIGNED_CUSTOMERS);
  62 + DashboardId dashboardId = new DashboardId(toUUID(record.get(ID), sql));
  63 + List<CustomerId> customerIds = new ArrayList<>();
  64 + if (!StringUtils.isEmpty(assignedCustomersString)) {
  65 + try {
  66 + JsonNode assignedCustomersJson = objectMapper.readTree(assignedCustomersString);
  67 + Map<String,String> assignedCustomers = objectMapper.treeToValue(assignedCustomersJson, HashMap.class);
  68 + assignedCustomers.forEach((strCustomerId, title) -> {
  69 + customerIds.add(new CustomerId(UUID.fromString(strCustomerId)));
  70 + });
  71 + } catch (IOException e) {
  72 + log.error("Unable to parse assigned customers field", e);
  73 + }
  74 + }
  75 + if (!StringUtils.isEmpty(customerIdString)) {
  76 + customerIds.add(new CustomerId(toUUID(customerIdString, sql)));
  77 + }
  78 + for (CustomerId customerId : customerIds) {
  79 + dashboardService.assignDashboardToCustomer(dashboardId, customerId);
  80 + }
  81 + });
  82 + }
  83 + }
  84 +
  85 + private static UUID toUUID(String src, boolean sql) {
  86 + if (sql) {
  87 + return UUIDConverter.fromString(src);
  88 + } else {
  89 + return UUID.fromString(src);
  90 + }
  91 + }
  92 +
  93 +}
... ...
... ... @@ -339,8 +339,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
339 339 JsonNode dashboardJson = objectMapper.readTree(path.toFile());
340 340 Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
341 341 dashboard.setTenantId(tenantId);
342   - dashboard.setCustomerId(customerId);
343   - dashboardService.saveDashboard(dashboard);
  342 + Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
  343 + if (customerId != null && !customerId.isNullUid()) {
  344 + dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId);
  345 + }
344 346 } catch (Exception e) {
345 347 log.error("Unable to load dashboard from json: [{}]", path.toString());
346 348 throw new RuntimeException("Unable to load dashboard from json", e);
... ...
... ... @@ -17,18 +17,26 @@
17 17 package org.thingsboard.server.service.install;
18 18
19 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.beans.factory.annotation.Autowired;
20 21 import org.springframework.beans.factory.annotation.Value;
21 22 import org.springframework.context.annotation.Profile;
22 23 import org.springframework.stereotype.Service;
  24 +import org.thingsboard.server.dao.dashboard.DashboardService;
23 25 import org.thingsboard.server.dao.util.SqlDao;
  26 +import org.thingsboard.server.service.install.cql.CassandraDbHelper;
  27 +import org.thingsboard.server.service.install.sql.SqlDbHelper;
24 28
25 29 import java.nio.charset.Charset;
26 30 import java.nio.file.Files;
27 31 import java.nio.file.Path;
28 32 import java.nio.file.Paths;
29 33 import java.sql.Connection;
  34 +import java.sql.DatabaseMetaData;
30 35 import java.sql.DriverManager;
31 36
  37 +import static org.thingsboard.server.service.install.DatabaseHelper.*;
  38 +import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION;
  39 +
32 40 @Service
33 41 @Profile("install")
34 42 @Slf4j
... ... @@ -49,6 +57,9 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
49 57 @Value("${spring.datasource.password}")
50 58 private String dbPassword;
51 59
  60 + @Autowired
  61 + private DashboardService dashboardService;
  62 +
52 63 @Override
53 64 public void upgradeDatabase(String fromVersion) throws Exception {
54 65 switch (fromVersion) {
... ... @@ -62,13 +73,30 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
62 73 log.info("Schema updated.");
63 74 break;
64 75 case "1.3.1":
65   - log.info("Updating schema ...");
66   - schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
67 76 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  77 +
  78 + log.info("Dumping dashboards ...");
  79 + Path dashboardsDump = SqlDbHelper.dumpTableIfExists(conn, DASHBOARD,
  80 + new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION},
  81 + new String[]{"", "", "", "", "", "", ""},
  82 + "tb-dashboards");
  83 + log.info("Dashboards dumped.");
  84 +
  85 + log.info("Updating schema ...");
  86 + schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
68 87 String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
69 88 conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  89 + log.info("Schema updated.");
  90 +
  91 + log.info("Restoring dashboards ...");
  92 + if (dashboardsDump != null) {
  93 + SqlDbHelper.loadTable(conn, DASHBOARD,
  94 + new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump);
  95 + DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, true);
  96 + Files.deleteIfExists(dashboardsDump);
  97 + }
  98 + log.info("Dashboards restored.");
70 99 }
71   - log.info("Schema updated.");
72 100 break;
73 101 default:
74 102 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
... ...
... ... @@ -17,7 +17,6 @@
17 17 package org.thingsboard.server.service.install.cql;
18 18
19 19 import com.datastax.driver.core.*;
20   -import org.apache.commons.csv.CSVFormat;
21 20 import org.apache.commons.csv.CSVParser;
22 21 import org.apache.commons.csv.CSVPrinter;
23 22 import org.apache.commons.csv.CSVRecord;
... ... @@ -28,9 +27,9 @@ import java.nio.file.Path;
28 27 import java.nio.file.StandardCopyOption;
29 28 import java.util.*;
30 29
31   -public class CassandraDbHelper {
  30 +import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT;
32 31
33   - private static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
  32 +public class CassandraDbHelper {
34 33
35 34 public static Path dumpCfIfExists(KeyspaceMetadata ks, Session session, String cfName,
36 35 String[] columns, String[] defaultValues, String dumpPrefix) throws Exception {
... ...
  1 +/**
  2 + * Copyright © 2016-2017 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 +package org.thingsboard.server.service.install.sql;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.apache.commons.csv.CSVParser;
  20 +import org.apache.commons.csv.CSVPrinter;
  21 +import org.apache.commons.csv.CSVRecord;
  22 +
  23 +import java.nio.file.Files;
  24 +import java.nio.file.Path;
  25 +import java.sql.*;
  26 +import java.util.ArrayList;
  27 +import java.util.HashMap;
  28 +import java.util.List;
  29 +import java.util.Map;
  30 +
  31 +import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT;
  32 +
  33 +/**
  34 + * Created by igor on 2/27/18.
  35 + */
  36 +@Slf4j
  37 +public class SqlDbHelper {
  38 +
  39 + public static Path dumpTableIfExists(Connection conn, String tableName,
  40 + String[] columns, String[] defaultValues, String dumpPrefix) throws Exception {
  41 +
  42 + DatabaseMetaData metaData = conn.getMetaData();
  43 + ResultSet res = metaData.getTables(null, null, tableName,
  44 + new String[] {"TABLE"});
  45 + if (res.next()) {
  46 + res.close();
  47 + Path dumpFile = Files.createTempFile(dumpPrefix, null);
  48 + Files.deleteIfExists(dumpFile);
  49 + try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), CSV_DUMP_FORMAT)) {
  50 + try (PreparedStatement stmt = conn.prepareStatement("SELECT * FROM " + tableName)) {
  51 + try (ResultSet tableRes = stmt.executeQuery()) {
  52 + ResultSetMetaData resMetaData = tableRes.getMetaData();
  53 + Map<String, Integer> columnIndexMap = new HashMap<>();
  54 + for (int i = 0; i < resMetaData.getColumnCount(); i++) {
  55 + String columnName = resMetaData.getColumnName(i);
  56 + columnIndexMap.put(columnName, i);
  57 + }
  58 + while(tableRes.next()) {
  59 + dumpRow(tableRes, columnIndexMap, columns, defaultValues, csvPrinter);
  60 + }
  61 + }
  62 + }
  63 + }
  64 + return dumpFile;
  65 + } else {
  66 + return null;
  67 + }
  68 + }
  69 +
  70 + public static void loadTable(Connection conn, String tableName, String[] columns, Path sourceFile) throws Exception {
  71 + PreparedStatement prepared = conn.prepareStatement(createInsertStatement(tableName, columns));
  72 + prepared.getParameterMetaData();
  73 + try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), CSV_DUMP_FORMAT.withHeader(columns))) {
  74 + csvParser.forEach(record -> {
  75 + try {
  76 + for (int i=0;i<columns.length;i++) {
  77 + setColumnValue(i, columns[i], record, prepared);
  78 + }
  79 + prepared.execute();
  80 + } catch (SQLException e) {
  81 + log.error("Unable to load table record!", e);
  82 + }
  83 + });
  84 + }
  85 + }
  86 +
  87 + private static void dumpRow(ResultSet res, Map<String, Integer> columnIndexMap, String[] columns,
  88 + String[] defaultValues, CSVPrinter csvPrinter) throws Exception {
  89 + List<String> record = new ArrayList<>();
  90 + for (int i=0;i<columns.length;i++) {
  91 + String column = columns[i];
  92 + String defaultValue;
  93 + if (defaultValues != null && i < defaultValues.length) {
  94 + defaultValue = defaultValues[i];
  95 + } else {
  96 + defaultValue = "";
  97 + }
  98 + record.add(getColumnValue(column, defaultValue, columnIndexMap, res));
  99 + }
  100 + csvPrinter.printRecord(record);
  101 + }
  102 +
  103 + private static String getColumnValue(String column, String defaultValue, Map<String, Integer> columnIndexMap, ResultSet res) {
  104 + int index = columnIndexMap.containsKey(column) ? columnIndexMap.get(column) : -1;
  105 + if (index > -1) {
  106 + String str;
  107 + try {
  108 + Object obj = res.getObject(index);
  109 + if (obj == null) {
  110 + str = "";
  111 + } else {
  112 + str = obj.toString();
  113 + }
  114 + } catch (Exception e) {
  115 + str = "";
  116 + }
  117 + return str;
  118 + } else {
  119 + return defaultValue;
  120 + }
  121 + }
  122 +
  123 + private static void setColumnValue(int index, String column,
  124 + CSVRecord record, PreparedStatement preparedStatement) throws SQLException {
  125 + String value = record.get(column);
  126 + int type = preparedStatement.getParameterMetaData().getParameterType(index + 1);
  127 + preparedStatement.setObject(index + 1, value, type);
  128 + }
  129 +
  130 + private static String createInsertStatement(String tableName, String[] columns) {
  131 + StringBuilder insertStatementBuilder = new StringBuilder();
  132 + insertStatementBuilder.append("INSERT INTO ").append(tableName).append(" (");
  133 + for (String column : columns) {
  134 + insertStatementBuilder.append(column).append(",");
  135 + }
  136 + insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
  137 + insertStatementBuilder.append(") VALUES (");
  138 + for (String column : columns) {
  139 + insertStatementBuilder.append("?").append(",");
  140 + }
  141 + insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
  142 + insertStatementBuilder.append(")");
  143 + return insertStatementBuilder.toString();
  144 + }
  145 +
  146 +}
... ...
... ... @@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.containsString;
19 19 import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
20 20 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
21 21
  22 +import java.sql.Time;
22 23 import java.util.ArrayList;
23 24 import java.util.Collections;
24 25 import java.util.List;
... ... @@ -29,6 +30,8 @@ import org.thingsboard.server.common.data.*;
29 30 import org.thingsboard.server.common.data.id.CustomerId;
30 31 import org.thingsboard.server.common.data.page.TextPageData;
31 32 import org.thingsboard.server.common.data.page.TextPageLink;
  33 +import org.thingsboard.server.common.data.page.TimePageData;
  34 +import org.thingsboard.server.common.data.page.TimePageLink;
32 35 import org.thingsboard.server.common.data.security.Authority;
33 36 import org.thingsboard.server.dao.model.ModelConstants;
34 37 import org.junit.After;
... ... @@ -82,8 +85,6 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
82 85 Assert.assertNotNull(savedDashboard.getId());
83 86 Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
84 87 Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId());
85   - Assert.assertNotNull(savedDashboard.getCustomerId());
86   - Assert.assertEquals(NULL_UUID, savedDashboard.getCustomerId().getId());
87 88 Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
88 89
89 90 savedDashboard.setTitle("My new dashboard");
... ... @@ -136,17 +137,20 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
136 137
137 138 Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
138 139 + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
139   - Assert.assertEquals(savedCustomer.getId(), assignedDashboard.getCustomerId());
140   -
  140 +
  141 + Assert.assertTrue(assignedDashboard.getAssignedCustomers().containsKey(savedCustomer.getId().toString()));
  142 +
141 143 Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
142   - Assert.assertEquals(savedCustomer.getId(), foundDashboard.getCustomerId());
  144 + Assert.assertTrue(foundDashboard.getAssignedCustomers().containsKey(savedCustomer.getId().toString()));
143 145
144 146 Dashboard unassignedDashboard =
145   - doDelete("/api/customer/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
146   - Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDashboard.getCustomerId().getId());
147   -
  147 + doDelete("/api/customer/"+savedCustomer.getId().getId().toString()+"/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
  148 +
  149 + Assert.assertTrue(unassignedDashboard.getAssignedCustomers() == null || unassignedDashboard.getAssignedCustomers().isEmpty());
  150 +
148 151 foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
149   - Assert.assertEquals(ModelConstants.NULL_UUID, foundDashboard.getCustomerId().getId());
  152 +
  153 + Assert.assertTrue(foundDashboard.getAssignedCustomers() == null || foundDashboard.getAssignedCustomers().isEmpty());
150 154 }
151 155
152 156 @Test
... ... @@ -320,11 +324,11 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
320 324 }
321 325
322 326 List<DashboardInfo> loadedDashboards = new ArrayList<>();
323   - TextPageLink pageLink = new TextPageLink(21);
324   - TextPageData<DashboardInfo> pageData = null;
  327 + TimePageLink pageLink = new TimePageLink(21);
  328 + TimePageData<DashboardInfo> pageData = null;
325 329 do {
326   - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
327   - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
  330 + pageData = doGetTypedWithTimePageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
  331 + new TypeReference<TimePageData<DashboardInfo>>(){}, pageLink);
328 332 loadedDashboards.addAll(pageData.getData());
329 333 if (pageData.hasNext()) {
330 334 pageLink = pageData.getNextPageLink();
... ... @@ -336,93 +340,5 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
336 340
337 341 Assert.assertEquals(dashboards, loadedDashboards);
338 342 }
339   -
340   - @Test
341   - public void testFindCustomerDashboardsByTitle() throws Exception {
342   - Customer customer = new Customer();
343   - customer.setTitle("Test customer");
344   - customer = doPost("/api/customer", customer, Customer.class);
345   - CustomerId customerId = customer.getId();
346   -
347   - String title1 = "Dashboard title 1";
348   - List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
349   - for (int i=0;i<125;i++) {
350   - Dashboard dashboard = new Dashboard();
351   - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
352   - String title = title1+suffix;
353   - title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
354   - dashboard.setTitle(title);
355   - dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
356   - dashboardsTitle1.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
357   - + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
358   - }
359   - String title2 = "Dashboard title 2";
360   - List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
361   - for (int i=0;i<143;i++) {
362   - Dashboard dashboard = new Dashboard();
363   - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
364   - String title = title2+suffix;
365   - title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
366   - dashboard.setTitle(title);
367   - dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
368   - dashboardsTitle2.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
369   - + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
370   - }
371   -
372   - List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
373   - TextPageLink pageLink = new TextPageLink(18, title1);
374   - TextPageData<DashboardInfo> pageData = null;
375   - do {
376   - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
377   - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
378   - loadedDashboardsTitle1.addAll(pageData.getData());
379   - if (pageData.hasNext()) {
380   - pageLink = pageData.getNextPageLink();
381   - }
382   - } while (pageData.hasNext());
383   -
384   - Collections.sort(dashboardsTitle1, idComparator);
385   - Collections.sort(loadedDashboardsTitle1, idComparator);
386   -
387   - Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
388   -
389   - List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
390   - pageLink = new TextPageLink(7, title2);
391   - do {
392   - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
393   - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
394   - loadedDashboardsTitle2.addAll(pageData.getData());
395   - if (pageData.hasNext()) {
396   - pageLink = pageData.getNextPageLink();
397   - }
398   - } while (pageData.hasNext());
399   -
400   - Collections.sort(dashboardsTitle2, idComparator);
401   - Collections.sort(loadedDashboardsTitle2, idComparator);
402   -
403   - Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
404   -
405   - for (DashboardInfo dashboard : loadedDashboardsTitle1) {
406   - doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())
407   - .andExpect(status().isOk());
408   - }
409   -
410   - pageLink = new TextPageLink(5, title1);
411   - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
412   - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
413   - Assert.assertFalse(pageData.hasNext());
414   - Assert.assertEquals(0, pageData.getData().size());
415   -
416   - for (DashboardInfo dashboard : loadedDashboardsTitle2) {
417   - doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())
418   - .andExpect(status().isOk());
419   - }
420   -
421   - pageLink = new TextPageLink(9, title2);
422   - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
423   - new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
424   - Assert.assertFalse(pageData.hasNext());
425   - Assert.assertEquals(0, pageData.getData().size());
426   - }
427 343
428 344 }
... ...
... ... @@ -79,8 +79,6 @@ public class Dashboard extends DashboardInfo {
79 79 StringBuilder builder = new StringBuilder();
80 80 builder.append("Dashboard [tenantId=");
81 81 builder.append(getTenantId());
82   - builder.append(", customerId=");
83   - builder.append(getCustomerId());
84 82 builder.append(", title=");
85 83 builder.append(getTitle());
86 84 builder.append(", configuration=");
... ...
... ... @@ -20,11 +20,16 @@ import org.thingsboard.server.common.data.id.CustomerId;
20 20 import org.thingsboard.server.common.data.id.DashboardId;
21 21 import org.thingsboard.server.common.data.id.TenantId;
22 22
  23 +import java.util.HashMap;
  24 +import java.util.List;
  25 +import java.util.Map;
  26 +import java.util.Set;
  27 +
23 28 public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName {
24 29
25 30 private TenantId tenantId;
26   - private CustomerId customerId;
27 31 private String title;
  32 + private Map<String, String> assignedCustomers;
28 33
29 34 public DashboardInfo() {
30 35 super();
... ... @@ -37,8 +42,8 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
37 42 public DashboardInfo(DashboardInfo dashboardInfo) {
38 43 super(dashboardInfo);
39 44 this.tenantId = dashboardInfo.getTenantId();
40   - this.customerId = dashboardInfo.getCustomerId();
41 45 this.title = dashboardInfo.getTitle();
  46 + this.assignedCustomers = dashboardInfo.getAssignedCustomers();
42 47 }
43 48
44 49 public TenantId getTenantId() {
... ... @@ -49,14 +54,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
49 54 this.tenantId = tenantId;
50 55 }
51 56
52   - public CustomerId getCustomerId() {
53   - return customerId;
54   - }
55   -
56   - public void setCustomerId(CustomerId customerId) {
57   - this.customerId = customerId;
58   - }
59   -
60 57 public String getTitle() {
61 58 return title;
62 59 }
... ... @@ -65,6 +62,44 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
65 62 this.title = title;
66 63 }
67 64
  65 + public Map<String, String> getAssignedCustomers() {
  66 + return assignedCustomers;
  67 + }
  68 +
  69 + public void setAssignedCustomers(Map<String, String> assignedCustomers) {
  70 + this.assignedCustomers = assignedCustomers;
  71 + }
  72 +
  73 + public boolean addAssignedCustomer(CustomerId customerId, String title) {
  74 + if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) {
  75 + return false;
  76 + } else {
  77 + if (this.assignedCustomers == null) {
  78 + this.assignedCustomers = new HashMap<>();
  79 + }
  80 + this.assignedCustomers.put(customerId.toString(), title);
  81 + return true;
  82 + }
  83 + }
  84 +
  85 + public boolean updateAssignedCustomer(CustomerId customerId, String title) {
  86 + if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) {
  87 + this.assignedCustomers.put(customerId.toString(), title);
  88 + return true;
  89 + } else {
  90 + return false;
  91 + }
  92 + }
  93 +
  94 + public boolean removeAssignedCustomer(CustomerId customerId) {
  95 + if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) {
  96 + this.assignedCustomers.remove(customerId.toString());
  97 + return true;
  98 + } else {
  99 + return false;
  100 + }
  101 + }
  102 +
68 103 @Override
69 104 @JsonProperty(access = JsonProperty.Access.READ_ONLY)
70 105 public String getName() {
... ... @@ -80,7 +115,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
80 115 public int hashCode() {
81 116 final int prime = 31;
82 117 int result = super.hashCode();
83   - result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
84 118 result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
85 119 result = prime * result + ((title == null) ? 0 : title.hashCode());
86 120 return result;
... ... @@ -95,11 +129,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
95 129 if (getClass() != obj.getClass())
96 130 return false;
97 131 DashboardInfo other = (DashboardInfo) obj;
98   - if (customerId == null) {
99   - if (other.customerId != null)
100   - return false;
101   - } else if (!customerId.equals(other.customerId))
102   - return false;
103 132 if (tenantId == null) {
104 133 if (other.tenantId != null)
105 134 return false;
... ... @@ -118,8 +147,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
118 147 StringBuilder builder = new StringBuilder();
119 148 builder.append("DashboardInfo [tenantId=");
120 149 builder.append(tenantId);
121   - builder.append(", customerId=");
122   - builder.append(customerId);
123 150 builder.append(", title=");
124 151 builder.append(title);
125 152 builder.append("]");
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.relation;
18 18 public enum RelationTypeGroup {
19 19
20 20 COMMON,
21   - ALARM
  21 + ALARM,
  22 + DASHBOARD
22 23
23 24 }
... ...
... ... @@ -97,7 +97,9 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
97 97 public Customer saveCustomer(Customer customer) {
98 98 log.trace("Executing saveCustomer [{}]", customer);
99 99 customerValidator.validate(customer);
100   - return customerDao.save(customer);
  100 + Customer savedCustomer = customerDao.save(customer);
  101 + dashboardService.updateCustomerDashboards(savedCustomer.getTenantId(), savedCustomer.getId(), savedCustomer.getTitle());
  102 + return savedCustomer;
101 103 }
102 104
103 105 @Override
... ...
... ... @@ -15,16 +15,26 @@
15 15 */
16 16 package org.thingsboard.server.dao.dashboard;
17 17
  18 +import com.google.common.util.concurrent.AsyncFunction;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
18 21 import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.beans.factory.annotation.Autowired;
19 23 import org.springframework.stereotype.Component;
20 24 import org.thingsboard.server.common.data.DashboardInfo;
  25 +import org.thingsboard.server.common.data.EntityType;
  26 +import org.thingsboard.server.common.data.id.CustomerId;
21 27 import org.thingsboard.server.common.data.page.TextPageLink;
  28 +import org.thingsboard.server.common.data.page.TimePageLink;
  29 +import org.thingsboard.server.common.data.relation.EntityRelation;
  30 +import org.thingsboard.server.common.data.relation.RelationTypeGroup;
22 31 import org.thingsboard.server.dao.DaoUtil;
23 32 import org.thingsboard.server.dao.model.nosql.DashboardInfoEntity;
24 33 import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
  34 +import org.thingsboard.server.dao.relation.RelationDao;
25 35 import org.thingsboard.server.dao.util.NoSqlDao;
26 36
27   -import java.util.Arrays;
  37 +import java.util.ArrayList;
28 38 import java.util.Collections;
29 39 import java.util.List;
30 40 import java.util.UUID;
... ... @@ -37,6 +47,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
37 47 @NoSqlDao
38 48 public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
39 49
  50 + @Autowired
  51 + private RelationDao relationDao;
  52 +
40 53 @Override
41 54 protected Class<DashboardInfoEntity> getColumnFamilyClass() {
42 55 return DashboardInfoEntity.class;
... ... @@ -59,15 +72,18 @@ public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<Da
59 72 }
60 73
61 74 @Override
62   - public List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
  75 + public ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) {
63 76 log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
64   - List<DashboardInfoEntity> dashboardEntities = findPageWithTextSearch(DASHBOARD_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
65   - Arrays.asList(eq(DASHBOARD_CUSTOMER_ID_PROPERTY, customerId),
66   - eq(DASHBOARD_TENANT_ID_PROPERTY, tenantId)),
67   - pageLink);
68 77
69   - log.trace("Found dashboards [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", dashboardEntities, tenantId, customerId, pageLink);
70   - return DaoUtil.convertDataList(dashboardEntities);
  78 + ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink);
  79 +
  80 + return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<DashboardInfo>>) input -> {
  81 + List<ListenableFuture<DashboardInfo>> dashboardFutures = new ArrayList<>(input.size());
  82 + for (EntityRelation relation : input) {
  83 + dashboardFutures.add(findByIdAsync(relation.getTo().getId()));
  84 + }
  85 + return Futures.successfulAsList(dashboardFutures);
  86 + });
71 87 }
72 88
73 89 }
... ...
... ... @@ -15,8 +15,10 @@
15 15 */
16 16 package org.thingsboard.server.dao.dashboard;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 19 import org.thingsboard.server.common.data.DashboardInfo;
19 20 import org.thingsboard.server.common.data.page.TextPageLink;
  21 +import org.thingsboard.server.common.data.page.TimePageLink;
20 22 import org.thingsboard.server.dao.Dao;
21 23
22 24 import java.util.List;
... ... @@ -44,6 +46,6 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> {
44 46 * @param pageLink the page link
45 47 * @return the list of dashboard objects
46 48 */
47   - List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
  49 + ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink);
48 50
49 51 }
... ...
... ... @@ -23,6 +23,10 @@ import org.thingsboard.server.common.data.id.DashboardId;
23 23 import org.thingsboard.server.common.data.id.TenantId;
24 24 import org.thingsboard.server.common.data.page.TextPageData;
25 25 import org.thingsboard.server.common.data.page.TextPageLink;
  26 +import org.thingsboard.server.common.data.page.TimePageData;
  27 +import org.thingsboard.server.common.data.page.TimePageLink;
  28 +
  29 +import java.sql.Time;
26 30
27 31 public interface DashboardService {
28 32
... ... @@ -38,7 +42,7 @@ public interface DashboardService {
38 42
39 43 Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId);
40 44
41   - Dashboard unassignDashboardFromCustomer(DashboardId dashboardId);
  45 + Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId);
42 46
43 47 void deleteDashboard(DashboardId dashboardId);
44 48
... ... @@ -46,8 +50,10 @@ public interface DashboardService {
46 50
47 51 void deleteDashboardsByTenantId(TenantId tenantId);
48 52
49   - TextPageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
  53 + ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink);
50 54
51 55 void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId);
52 56
  57 + void updateCustomerDashboards(TenantId tenantId, CustomerId customerId, String customerTitle);
  58 +
53 59 }
... ...
... ... @@ -15,30 +15,42 @@
15 15 */
16 16 package org.thingsboard.server.dao.dashboard;
17 17
  18 +import com.google.common.base.Function;
  19 +import com.google.common.util.concurrent.Futures;
18 20 import com.google.common.util.concurrent.ListenableFuture;
19 21 import lombok.extern.slf4j.Slf4j;
20 22 import org.apache.commons.lang3.StringUtils;
21 23 import org.springframework.beans.factory.annotation.Autowired;
22 24 import org.springframework.stereotype.Service;
23   -import org.thingsboard.server.common.data.Customer;
24   -import org.thingsboard.server.common.data.Dashboard;
25   -import org.thingsboard.server.common.data.DashboardInfo;
26   -import org.thingsboard.server.common.data.Tenant;
  25 +import org.thingsboard.server.common.data.*;
  26 +import org.thingsboard.server.common.data.alarm.AlarmInfo;
27 27 import org.thingsboard.server.common.data.id.CustomerId;
28 28 import org.thingsboard.server.common.data.id.DashboardId;
29 29 import org.thingsboard.server.common.data.id.TenantId;
30 30 import org.thingsboard.server.common.data.page.TextPageData;
31 31 import org.thingsboard.server.common.data.page.TextPageLink;
  32 +import org.thingsboard.server.common.data.page.TimePageData;
  33 +import org.thingsboard.server.common.data.page.TimePageLink;
  34 +import org.thingsboard.server.common.data.relation.EntityRelation;
  35 +import org.thingsboard.server.common.data.relation.RelationTypeGroup;
32 36 import org.thingsboard.server.dao.customer.CustomerDao;
33 37 import org.thingsboard.server.dao.entity.AbstractEntityService;
34 38 import org.thingsboard.server.dao.exception.DataValidationException;
35 39 import org.thingsboard.server.dao.model.ModelConstants;
  40 +import org.thingsboard.server.dao.relation.RelationDao;
36 41 import org.thingsboard.server.dao.service.DataValidator;
37 42 import org.thingsboard.server.dao.service.PaginatedRemover;
  43 +import org.thingsboard.server.dao.service.TimePaginatedRemover;
38 44 import org.thingsboard.server.dao.service.Validator;
39 45 import org.thingsboard.server.dao.tenant.TenantDao;
40 46
  47 +import javax.annotation.Nullable;
  48 +import java.sql.Time;
  49 +import java.util.ArrayList;
  50 +import java.util.HashSet;
41 51 import java.util.List;
  52 +import java.util.Set;
  53 +import java.util.concurrent.ExecutionException;
42 54
43 55 import static org.thingsboard.server.dao.service.Validator.validateId;
44 56
... ... @@ -59,7 +71,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
59 71
60 72 @Autowired
61 73 private CustomerDao customerDao;
62   -
  74 +
63 75 @Override
64 76 public Dashboard findDashboardById(DashboardId dashboardId) {
65 77 log.trace("Executing findDashboardById [{}]", dashboardId);
... ... @@ -98,15 +110,59 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
98 110 @Override
99 111 public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId) {
100 112 Dashboard dashboard = findDashboardById(dashboardId);
101   - dashboard.setCustomerId(customerId);
102   - return saveDashboard(dashboard);
  113 + Customer customer = customerDao.findById(customerId.getId());
  114 + if (customer == null) {
  115 + throw new DataValidationException("Can't assign dashboard to non-existent customer!");
  116 + }
  117 + if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) {
  118 + throw new DataValidationException("Can't assign dashboard to customer from different tenant!");
  119 + }
  120 + if (dashboard.addAssignedCustomer(customerId, customer.getTitle())) {
  121 + try {
  122 + createRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
  123 + } catch (ExecutionException | InterruptedException e) {
  124 + log.warn("[{}] Failed to create dashboard relation. Customer Id: [{}]", dashboardId, customerId);
  125 + throw new RuntimeException(e);
  126 + }
  127 + return saveDashboard(dashboard);
  128 + } else {
  129 + return dashboard;
  130 + }
103 131 }
104 132
105 133 @Override
106   - public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId) {
  134 + public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId) {
107 135 Dashboard dashboard = findDashboardById(dashboardId);
108   - dashboard.setCustomerId(null);
109   - return saveDashboard(dashboard);
  136 + if (dashboard.removeAssignedCustomer(customerId)) {
  137 + try {
  138 + deleteRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
  139 + } catch (ExecutionException | InterruptedException e) {
  140 + log.warn("[{}] Failed to delete dashboard relation. Customer Id: [{}]", dashboardId, customerId);
  141 + throw new RuntimeException(e);
  142 + }
  143 + return saveDashboard(dashboard);
  144 + } else {
  145 + return dashboard;
  146 + }
  147 + }
  148 +
  149 + private Dashboard updateAssignedCustomerTitle(DashboardId dashboardId, CustomerId customerId, String customerTitle) {
  150 + Dashboard dashboard = findDashboardById(dashboardId);
  151 + if (dashboard.updateAssignedCustomer(customerId, customerTitle)) {
  152 + return saveDashboard(dashboard);
  153 + } else {
  154 + return dashboard;
  155 + }
  156 + }
  157 +
  158 + private void deleteRelation(EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
  159 + log.debug("Deleting Dashboard relation: {}", dashboardRelation);
  160 + relationService.deleteRelationAsync(dashboardRelation).get();
  161 + }
  162 +
  163 + private void createRelation(EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
  164 + log.debug("Creating Dashboard relation: {}", dashboardRelation);
  165 + relationService.saveRelationAsync(dashboardRelation).get();
110 166 }
111 167
112 168 @Override
... ... @@ -134,13 +190,20 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
134 190 }
135 191
136 192 @Override
137   - public TextPageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) {
  193 + public ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) {
138 194 log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink);
139 195 Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
140 196 Validator.validateId(customerId, "Incorrect customerId " + customerId);
141 197 Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
142   - List<DashboardInfo> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
143   - return new TextPageData<>(dashboards, pageLink);
  198 + ListenableFuture<List<DashboardInfo>> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
  199 +
  200 + return Futures.transform(dashboards, new Function<List<DashboardInfo>, TimePageData<DashboardInfo>>() {
  201 + @Nullable
  202 + @Override
  203 + public TimePageData<DashboardInfo> apply(@Nullable List<DashboardInfo> dashboards) {
  204 + return new TimePageData<>(dashboards, pageLink);
  205 + }
  206 + });
144 207 }
145 208
146 209 @Override
... ... @@ -148,9 +211,18 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
148 211 log.trace("Executing unassignCustomerDashboards, tenantId [{}], customerId [{}]", tenantId, customerId);
149 212 Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
150 213 Validator.validateId(customerId, "Incorrect customerId " + customerId);
151   - new CustomerDashboardsUnassigner(tenantId).removeEntities(customerId);
  214 + new CustomerDashboardsUnassigner(tenantId, customerId).removeEntities(customerId);
152 215 }
153   -
  216 +
  217 + @Override
  218 + public void updateCustomerDashboards(TenantId tenantId, CustomerId customerId, String customerTitle) {
  219 + log.trace("Executing updateCustomerDashboards, tenantId [{}], customerId [{}], customerTitle [{}]", tenantId, customerId, customerTitle);
  220 + Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  221 + Validator.validateId(customerId, "Incorrect customerId " + customerId);
  222 + Validator.validateString(customerTitle, "Incorrect customerTitle " + customerTitle);
  223 + new CustomerDashboardsUpdater(tenantId, customerId, customerTitle).removeEntities(customerId);
  224 + }
  225 +
154 226 private DataValidator<Dashboard> dashboardValidator =
155 227 new DataValidator<Dashboard>() {
156 228 @Override
... ... @@ -166,17 +238,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
166 238 throw new DataValidationException("Dashboard is referencing to non-existent tenant!");
167 239 }
168 240 }
169   - if (dashboard.getCustomerId() == null) {
170   - dashboard.setCustomerId(new CustomerId(ModelConstants.NULL_UUID));
171   - } else if (!dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
172   - Customer customer = customerDao.findById(dashboard.getCustomerId().getId());
173   - if (customer == null) {
174   - throw new DataValidationException("Can't assign dashboard to non-existent customer!");
175   - }
176   - if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) {
177   - throw new DataValidationException("Can't assign dashboard to customer from different tenant!");
178   - }
179   - }
180 241 }
181 242 };
182 243
... ... @@ -194,24 +255,60 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
194 255 }
195 256 };
196 257
197   - private class CustomerDashboardsUnassigner extends PaginatedRemover<CustomerId, DashboardInfo> {
  258 + private class CustomerDashboardsUnassigner extends TimePaginatedRemover<CustomerId, DashboardInfo> {
198 259
199 260 private TenantId tenantId;
  261 + private CustomerId customerId;
200 262
201   - CustomerDashboardsUnassigner(TenantId tenantId) {
  263 + CustomerDashboardsUnassigner(TenantId tenantId, CustomerId customerId) {
202 264 this.tenantId = tenantId;
  265 + this.customerId = customerId;
203 266 }
204 267
205 268 @Override
206   - protected List<DashboardInfo> findEntities(CustomerId id, TextPageLink pageLink) {
207   - return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
  269 + protected List<DashboardInfo> findEntities(CustomerId id, TimePageLink pageLink) {
  270 + try {
  271 + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink).get();
  272 + } catch (InterruptedException | ExecutionException e) {
  273 + log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", tenantId, id);
  274 + throw new RuntimeException(e);
  275 + }
208 276 }
209 277
210 278 @Override
211 279 protected void removeEntity(DashboardInfo entity) {
212   - unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()));
  280 + unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()), this.customerId);
213 281 }
214 282
215 283 }
216 284
  285 + private class CustomerDashboardsUpdater extends TimePaginatedRemover<CustomerId, DashboardInfo> {
  286 +
  287 + private TenantId tenantId;
  288 + private CustomerId customerId;
  289 + private String customerTitle;
  290 +
  291 + CustomerDashboardsUpdater(TenantId tenantId, CustomerId customerId, String customerTitle) {
  292 + this.tenantId = tenantId;
  293 + this.customerId = customerId;
  294 + this.customerTitle = customerTitle;
  295 + }
  296 +
  297 + @Override
  298 + protected List<DashboardInfo> findEntities(CustomerId id, TimePageLink pageLink) {
  299 + try {
  300 + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink).get();
  301 + } catch (InterruptedException | ExecutionException e) {
  302 + log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", tenantId, id);
  303 + throw new RuntimeException(e);
  304 + }
  305 + }
  306 +
  307 + @Override
  308 + protected void removeEntity(DashboardInfo entity) {
  309 + updateAssignedCustomerTitle(new DashboardId(entity.getUuidId()), this.customerId, this.customerTitle);
  310 + }
  311 +
  312 + }
  313 +
217 314 }
... ...
... ... @@ -266,13 +266,11 @@ public class ModelConstants {
266 266 */
267 267 public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard";
268 268 public static final String DASHBOARD_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
269   - public static final String DASHBOARD_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
270 269 public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY;
271 270 public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration";
  271 + public static final String DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY = "assigned_customers";
272 272
273 273 public static final String DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_tenant_and_search_text";
274   - public static final String DASHBOARD_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_customer_and_search_text";
275   -
276 274
277 275 /**
278 276 * Cassandra plugin metadata constants.
... ...
... ... @@ -19,16 +19,19 @@ import com.datastax.driver.core.utils.UUIDs;
19 19 import com.datastax.driver.mapping.annotations.Column;
20 20 import com.datastax.driver.mapping.annotations.PartitionKey;
21 21 import com.datastax.driver.mapping.annotations.Table;
  22 +import com.fasterxml.jackson.core.JsonProcessingException;
22 23 import com.fasterxml.jackson.databind.JsonNode;
  24 +import com.fasterxml.jackson.databind.ObjectMapper;
23 25 import lombok.EqualsAndHashCode;
24 26 import lombok.ToString;
  27 +import lombok.extern.slf4j.Slf4j;
25 28 import org.thingsboard.server.common.data.Dashboard;
26   -import org.thingsboard.server.common.data.id.CustomerId;
27 29 import org.thingsboard.server.common.data.id.DashboardId;
28 30 import org.thingsboard.server.common.data.id.TenantId;
29 31 import org.thingsboard.server.dao.model.SearchTextEntity;
30 32 import org.thingsboard.server.dao.model.type.JsonCodec;
31 33
  34 +import java.util.HashMap;
32 35 import java.util.UUID;
33 36
34 37 import static org.thingsboard.server.dao.model.ModelConstants.*;
... ... @@ -36,8 +39,11 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
36 39 @Table(name = DASHBOARD_COLUMN_FAMILY_NAME)
37 40 @EqualsAndHashCode
38 41 @ToString
  42 +@Slf4j
39 43 public final class DashboardEntity implements SearchTextEntity<Dashboard> {
40   -
  44 +
  45 + private static final ObjectMapper objectMapper = new ObjectMapper();
  46 +
41 47 @PartitionKey(value = 0)
42 48 @Column(name = ID_PROPERTY)
43 49 private UUID id;
... ... @@ -46,16 +52,15 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
46 52 @Column(name = DASHBOARD_TENANT_ID_PROPERTY)
47 53 private UUID tenantId;
48 54
49   - @PartitionKey(value = 2)
50   - @Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)
51   - private UUID customerId;
52   -
53 55 @Column(name = DASHBOARD_TITLE_PROPERTY)
54 56 private String title;
55 57
56 58 @Column(name = SEARCH_TEXT_PROPERTY)
57 59 private String searchText;
58   -
  60 +
  61 + @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY, codec = JsonCodec.class)
  62 + private JsonNode assignedCustomers;
  63 +
59 64 @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
60 65 private JsonNode configuration;
61 66
... ... @@ -70,10 +75,10 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
70 75 if (dashboard.getTenantId() != null) {
71 76 this.tenantId = dashboard.getTenantId().getId();
72 77 }
73   - if (dashboard.getCustomerId() != null) {
74   - this.customerId = dashboard.getCustomerId().getId();
75   - }
76 78 this.title = dashboard.getTitle();
  79 + if (dashboard.getAssignedCustomers() != null) {
  80 + this.assignedCustomers = objectMapper.valueToTree(dashboard.getAssignedCustomers());
  81 + }
77 82 this.configuration = dashboard.getConfiguration();
78 83 }
79 84
... ... @@ -93,14 +98,6 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
93 98 this.tenantId = tenantId;
94 99 }
95 100
96   - public UUID getCustomerId() {
97   - return customerId;
98   - }
99   -
100   - public void setCustomerId(UUID customerId) {
101   - this.customerId = customerId;
102   - }
103   -
104 101 public String getTitle() {
105 102 return title;
106 103 }
... ... @@ -109,6 +106,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
109 106 this.title = title;
110 107 }
111 108
  109 + public JsonNode getAssignedCustomers() {
  110 + return assignedCustomers;
  111 + }
  112 +
  113 + public void setAssignedCustomers(JsonNode assignedCustomers) {
  114 + this.assignedCustomers = assignedCustomers;
  115 + }
  116 +
112 117 public JsonNode getConfiguration() {
113 118 return configuration;
114 119 }
... ... @@ -138,10 +143,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
138 143 if (tenantId != null) {
139 144 dashboard.setTenantId(new TenantId(tenantId));
140 145 }
141   - if (customerId != null) {
142   - dashboard.setCustomerId(new CustomerId(customerId));
143   - }
144 146 dashboard.setTitle(title);
  147 + if (assignedCustomers != null) {
  148 + try {
  149 + dashboard.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));
  150 + } catch (JsonProcessingException e) {
  151 + log.warn("Unable to parse assigned customers!", e);
  152 + }
  153 + }
145 154 dashboard.setConfiguration(configuration);
146 155 return dashboard;
147 156 }
... ...
... ... @@ -19,14 +19,21 @@ import com.datastax.driver.core.utils.UUIDs;
19 19 import com.datastax.driver.mapping.annotations.Column;
20 20 import com.datastax.driver.mapping.annotations.PartitionKey;
21 21 import com.datastax.driver.mapping.annotations.Table;
  22 +import com.fasterxml.jackson.core.JsonProcessingException;
  23 +import com.fasterxml.jackson.databind.JsonNode;
  24 +import com.fasterxml.jackson.databind.ObjectMapper;
22 25 import lombok.EqualsAndHashCode;
23 26 import lombok.ToString;
  27 +import lombok.extern.slf4j.Slf4j;
24 28 import org.thingsboard.server.common.data.DashboardInfo;
25 29 import org.thingsboard.server.common.data.id.CustomerId;
26 30 import org.thingsboard.server.common.data.id.DashboardId;
27 31 import org.thingsboard.server.common.data.id.TenantId;
28 32 import org.thingsboard.server.dao.model.SearchTextEntity;
  33 +import org.thingsboard.server.dao.model.type.JsonCodec;
29 34
  35 +import java.util.HashMap;
  36 +import java.util.Set;
30 37 import java.util.UUID;
31 38
32 39 import static org.thingsboard.server.dao.model.ModelConstants.*;
... ... @@ -34,8 +41,11 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
34 41 @Table(name = DASHBOARD_COLUMN_FAMILY_NAME)
35 42 @EqualsAndHashCode
36 43 @ToString
  44 +@Slf4j
37 45 public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
38 46
  47 + private static final ObjectMapper objectMapper = new ObjectMapper();
  48 +
39 49 @PartitionKey(value = 0)
40 50 @Column(name = ID_PROPERTY)
41 51 private UUID id;
... ... @@ -44,16 +54,15 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
44 54 @Column(name = DASHBOARD_TENANT_ID_PROPERTY)
45 55 private UUID tenantId;
46 56
47   - @PartitionKey(value = 2)
48   - @Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)
49   - private UUID customerId;
50   -
51 57 @Column(name = DASHBOARD_TITLE_PROPERTY)
52 58 private String title;
53 59
54 60 @Column(name = SEARCH_TEXT_PROPERTY)
55 61 private String searchText;
56 62
  63 + @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY, codec = JsonCodec.class)
  64 + private JsonNode assignedCustomers;
  65 +
57 66 public DashboardInfoEntity() {
58 67 super();
59 68 }
... ... @@ -65,10 +74,10 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
65 74 if (dashboardInfo.getTenantId() != null) {
66 75 this.tenantId = dashboardInfo.getTenantId().getId();
67 76 }
68   - if (dashboardInfo.getCustomerId() != null) {
69   - this.customerId = dashboardInfo.getCustomerId().getId();
70   - }
71 77 this.title = dashboardInfo.getTitle();
  78 + if (dashboardInfo.getAssignedCustomers() != null) {
  79 + this.assignedCustomers = objectMapper.valueToTree(dashboardInfo.getAssignedCustomers());
  80 + }
72 81 }
73 82
74 83 public UUID getId() {
... ... @@ -87,14 +96,6 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
87 96 this.tenantId = tenantId;
88 97 }
89 98
90   - public UUID getCustomerId() {
91   - return customerId;
92   - }
93   -
94   - public void setCustomerId(UUID customerId) {
95   - this.customerId = customerId;
96   - }
97   -
98 99 public String getTitle() {
99 100 return title;
100 101 }
... ... @@ -103,6 +104,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
103 104 this.title = title;
104 105 }
105 106
  107 + public JsonNode getAssignedCustomers() {
  108 + return assignedCustomers;
  109 + }
  110 +
  111 + public void setAssignedCustomers(JsonNode assignedCustomers) {
  112 + this.assignedCustomers = assignedCustomers;
  113 + }
  114 +
106 115 @Override
107 116 public String getSearchTextSource() {
108 117 return getTitle();
... ... @@ -124,10 +133,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
124 133 if (tenantId != null) {
125 134 dashboardInfo.setTenantId(new TenantId(tenantId));
126 135 }
127   - if (customerId != null) {
128   - dashboardInfo.setCustomerId(new CustomerId(customerId));
129   - }
130 136 dashboardInfo.setTitle(title);
  137 + if (assignedCustomers != null) {
  138 + try {
  139 + dashboardInfo.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));
  140 + } catch (JsonProcessingException e) {
  141 + log.warn("Unable to parse assigned customers!", e);
  142 + }
  143 + }
131 144 return dashboardInfo;
132 145 }
133 146
... ...
... ... @@ -16,9 +16,12 @@
16 16 package org.thingsboard.server.dao.model.sql;
17 17
18 18 import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.core.JsonProcessingException;
19 20 import com.fasterxml.jackson.databind.JsonNode;
  21 +import com.fasterxml.jackson.databind.ObjectMapper;
20 22 import lombok.Data;
21 23 import lombok.EqualsAndHashCode;
  24 +import lombok.extern.slf4j.Slf4j;
22 25 import org.hibernate.annotations.Type;
23 26 import org.hibernate.annotations.TypeDef;
24 27 import org.thingsboard.server.common.data.Dashboard;
... ... @@ -33,20 +36,23 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType;
33 36 import javax.persistence.Column;
34 37 import javax.persistence.Entity;
35 38 import javax.persistence.Table;
  39 +import java.util.HashMap;
  40 +import java.util.List;
  41 +import java.util.Set;
36 42
37 43 @Data
  44 +@Slf4j
38 45 @EqualsAndHashCode(callSuper = true)
39 46 @Entity
40 47 @TypeDef(name = "json", typeClass = JsonStringType.class)
41 48 @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
42 49 public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements SearchTextEntity<Dashboard> {
43 50
  51 + private static final ObjectMapper objectMapper = new ObjectMapper();
  52 +
44 53 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
45 54 private String tenantId;
46 55
47   - @Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
48   - private String customerId;
49   -
50 56 @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
51 57 private String title;
52 58
... ... @@ -54,6 +60,10 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
54 60 private String searchText;
55 61
56 62 @Type(type = "json")
  63 + @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  64 + private JsonNode assignedCustomers;
  65 +
  66 + @Type(type = "json")
57 67 @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY)
58 68 private JsonNode configuration;
59 69
... ... @@ -68,10 +78,10 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
68 78 if (dashboard.getTenantId() != null) {
69 79 this.tenantId = toString(dashboard.getTenantId().getId());
70 80 }
71   - if (dashboard.getCustomerId() != null) {
72   - this.customerId = toString(dashboard.getCustomerId().getId());
73   - }
74 81 this.title = dashboard.getTitle();
  82 + if (dashboard.getAssignedCustomers() != null) {
  83 + this.assignedCustomers = objectMapper.valueToTree(dashboard.getAssignedCustomers());
  84 + }
75 85 this.configuration = dashboard.getConfiguration();
76 86 }
77 87
... ... @@ -92,10 +102,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
92 102 if (tenantId != null) {
93 103 dashboard.setTenantId(new TenantId(toUUID(tenantId)));
94 104 }
95   - if (customerId != null) {
96   - dashboard.setCustomerId(new CustomerId(toUUID(customerId)));
97   - }
98 105 dashboard.setTitle(title);
  106 + if (assignedCustomers != null) {
  107 + try {
  108 + dashboard.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));
  109 + } catch (JsonProcessingException e) {
  110 + log.warn("Unable to parse assigned customers!", e);
  111 + }
  112 + }
99 113 dashboard.setConfiguration(configuration);
100 114 return dashboard;
101 115 }
... ...
... ... @@ -16,8 +16,13 @@
16 16 package org.thingsboard.server.dao.model.sql;
17 17
18 18 import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.core.JsonProcessingException;
  20 +import com.fasterxml.jackson.databind.JsonNode;
  21 +import com.fasterxml.jackson.databind.ObjectMapper;
19 22 import lombok.Data;
20 23 import lombok.EqualsAndHashCode;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.hibernate.annotations.Type;
21 26 import org.thingsboard.server.common.data.DashboardInfo;
22 27 import org.thingsboard.server.common.data.id.CustomerId;
23 28 import org.thingsboard.server.common.data.id.DashboardId;
... ... @@ -29,25 +34,31 @@ import org.thingsboard.server.dao.model.SearchTextEntity;
29 34 import javax.persistence.Column;
30 35 import javax.persistence.Entity;
31 36 import javax.persistence.Table;
  37 +import java.util.HashMap;
  38 +import java.util.Set;
32 39
33 40 @Data
  41 +@Slf4j
34 42 @EqualsAndHashCode(callSuper = true)
35 43 @Entity
36 44 @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
37 45 public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements SearchTextEntity<DashboardInfo> {
38 46
  47 + private static final ObjectMapper objectMapper = new ObjectMapper();
  48 +
39 49 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
40 50 private String tenantId;
41 51
42   - @Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
43   - private String customerId;
44   -
45 52 @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
46 53 private String title;
47 54
48 55 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
49 56 private String searchText;
50 57
  58 + @Type(type = "json")
  59 + @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  60 + private JsonNode assignedCustomers;
  61 +
51 62 public DashboardInfoEntity() {
52 63 super();
53 64 }
... ... @@ -59,10 +70,10 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
59 70 if (dashboardInfo.getTenantId() != null) {
60 71 this.tenantId = toString(dashboardInfo.getTenantId().getId());
61 72 }
62   - if (dashboardInfo.getCustomerId() != null) {
63   - this.customerId = toString(dashboardInfo.getCustomerId().getId());
64   - }
65 73 this.title = dashboardInfo.getTitle();
  74 + if (dashboardInfo.getAssignedCustomers() != null) {
  75 + this.assignedCustomers = objectMapper.valueToTree(dashboardInfo.getAssignedCustomers());
  76 + }
66 77 }
67 78
68 79 @Override
... ... @@ -86,10 +97,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
86 97 if (tenantId != null) {
87 98 dashboardInfo.setTenantId(new TenantId(toUUID(tenantId)));
88 99 }
89   - if (customerId != null) {
90   - dashboardInfo.setCustomerId(new CustomerId(toUUID(customerId)));
91   - }
92 100 dashboardInfo.setTitle(title);
  101 + if (assignedCustomers != null) {
  102 + try {
  103 + dashboardInfo.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));
  104 + } catch (JsonProcessingException e) {
  105 + log.warn("Unable to parse assigned customers!", e);
  106 + }
  107 + }
93 108 return dashboardInfo;
94 109 }
95 110
... ...
  1 +/**
  2 + * Copyright © 2016-2017 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 +package org.thingsboard.server.dao.service;
  17 +
  18 +import org.thingsboard.server.common.data.id.IdBased;
  19 +import org.thingsboard.server.common.data.page.TimePageLink;
  20 +
  21 +import java.sql.Time;
  22 +import java.util.List;
  23 +import java.util.UUID;
  24 +
  25 +public abstract class TimePaginatedRemover<I, D extends IdBased<?>> {
  26 +
  27 + private static final int DEFAULT_LIMIT = 100;
  28 +
  29 + public void removeEntities(I id) {
  30 + TimePageLink pageLink = new TimePageLink(DEFAULT_LIMIT);
  31 + boolean hasNext = true;
  32 + while (hasNext) {
  33 + List<D> entities = findEntities(id, pageLink);
  34 + for (D entity : entities) {
  35 + removeEntity(entity);
  36 + }
  37 + hasNext = entities.size() == pageLink.getLimit();
  38 + if (hasNext) {
  39 + int index = entities.size() - 1;
  40 + UUID idOffset = entities.get(index).getUuidId();
  41 + pageLink.setIdOffset(idOffset);
  42 + }
  43 + }
  44 + }
  45 +
  46 + protected abstract List<D> findEntities(I id, TimePageLink pageLink);
  47 +
  48 + protected abstract void removeEntity(D entity);
  49 +
  50 +}
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service;
17 17
18 18 import org.thingsboard.server.common.data.id.EntityId;
19 19 import org.thingsboard.server.common.data.id.UUIDBased;
  20 +import org.thingsboard.server.common.data.page.BasePageLink;
20 21 import org.thingsboard.server.common.data.page.TextPageLink;
21 22 import org.thingsboard.server.dao.exception.IncorrectParameterException;
22 23
... ... @@ -116,7 +117,7 @@ public class Validator {
116 117 * @param pageLink the page link
117 118 * @param errorMessage the error message for exception
118 119 */
119   - public static void validatePageLink(TextPageLink pageLink, String errorMessage) {
  120 + public static void validatePageLink(BasePageLink pageLink, String errorMessage) {
120 121 if (pageLink == null || pageLink.getLimit() < 1 || (pageLink.getIdOffset() != null && pageLink.getIdOffset().version() != 1)) {
121 122 throw new IncorrectParameterException(errorMessage);
122 123 }
... ...
... ... @@ -39,12 +39,4 @@ public interface DashboardInfoRepository extends CrudRepository<DashboardInfoEnt
39 39 @Param("idOffset") String idOffset,
40 40 Pageable pageable);
41 41
42   - @Query("SELECT di FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId " +
43   - "AND di.customerId = :customerId AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
44   - "AND di.id > :idOffset ORDER BY di.id")
45   - List<DashboardInfoEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
46   - @Param("customerId") String customerId,
47   - @Param("searchText") String searchText,
48   - @Param("idOffset") String idOffset,
49   - Pageable pageable);
50 42 }
... ...
... ... @@ -15,19 +15,31 @@
15 15 */
16 16 package org.thingsboard.server.dao.sql.dashboard;
17 17
  18 +import com.google.common.util.concurrent.AsyncFunction;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import lombok.extern.slf4j.Slf4j;
18 22 import org.springframework.beans.factory.annotation.Autowired;
19 23 import org.springframework.data.domain.PageRequest;
20 24 import org.springframework.data.repository.CrudRepository;
21 25 import org.springframework.stereotype.Component;
22 26 import org.thingsboard.server.common.data.DashboardInfo;
  27 +import org.thingsboard.server.common.data.EntityType;
23 28 import org.thingsboard.server.common.data.UUIDConverter;
  29 +import org.thingsboard.server.common.data.id.CustomerId;
24 30 import org.thingsboard.server.common.data.page.TextPageLink;
  31 +import org.thingsboard.server.common.data.page.TimePageLink;
  32 +import org.thingsboard.server.common.data.relation.EntityRelation;
  33 +import org.thingsboard.server.common.data.relation.RelationTypeGroup;
25 34 import org.thingsboard.server.dao.DaoUtil;
26 35 import org.thingsboard.server.dao.dashboard.DashboardInfoDao;
27 36 import org.thingsboard.server.dao.model.sql.DashboardInfoEntity;
  37 +import org.thingsboard.server.dao.relation.RelationDao;
28 38 import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
29 39 import org.thingsboard.server.dao.util.SqlDao;
30 40
  41 +import java.sql.Time;
  42 +import java.util.ArrayList;
31 43 import java.util.List;
32 44 import java.util.Objects;
33 45 import java.util.UUID;
... ... @@ -37,11 +49,15 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
37 49 /**
38 50 * Created by Valerii Sosliuk on 5/6/2017.
39 51 */
  52 +@Slf4j
40 53 @Component
41 54 @SqlDao
42 55 public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
43 56
44 57 @Autowired
  58 + private RelationDao relationDao;
  59 +
  60 + @Autowired
45 61 private DashboardInfoRepository dashboardInfoRepository;
46 62
47 63 @Override
... ... @@ -65,13 +81,17 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
65 81 }
66 82
67 83 @Override
68   - public List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
69   - return DaoUtil.convertDataList(dashboardInfoRepository
70   - .findByTenantIdAndCustomerId(
71   - UUIDConverter.fromTimeUUID(tenantId),
72   - UUIDConverter.fromTimeUUID(customerId),
73   - Objects.toString(pageLink.getTextSearch(), ""),
74   - pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()),
75   - new PageRequest(0, pageLink.getLimit())));
  84 + public ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) {
  85 + log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
  86 +
  87 + ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink);
  88 +
  89 + return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<DashboardInfo>>) input -> {
  90 + List<ListenableFuture<DashboardInfo>> dashboardFutures = new ArrayList<>(input.size());
  91 + for (EntityRelation relation : input) {
  92 + dashboardFutures.add(findByIdAsync(relation.getTo().getId()));
  93 + }
  94 + return Futures.successfulAsList(dashboardFutures);
  95 + });
76 96 }
77 97 }
... ...
... ... @@ -364,26 +364,19 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.widget_type_by_tenant_and_ali
364 364 CREATE TABLE IF NOT EXISTS thingsboard.dashboard (
365 365 id timeuuid,
366 366 tenant_id timeuuid,
367   - customer_id timeuuid,
368 367 title text,
369 368 search_text text,
  369 + assigned_customers text,
370 370 configuration text,
371   - PRIMARY KEY (id, tenant_id, customer_id)
  371 + PRIMARY KEY (id, tenant_id)
372 372 );
373 373
374 374 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
375 375 SELECT *
376 376 from thingsboard.dashboard
377   - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
378   - PRIMARY KEY ( tenant_id, search_text, id, customer_id )
379   - WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC );
380   -
381   -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_customer_and_search_text AS
382   - SELECT *
383   - from thingsboard.dashboard
384   - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
385   - PRIMARY KEY ( customer_id, tenant_id, search_text, id )
386   - WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
  377 + WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
  378 + PRIMARY KEY ( tenant_id, search_text, id )
  379 + WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
387 380
388 381 CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf (
389 382 entity_type text, // (DEVICE, CUSTOMER, TENANT)
... ...
... ... @@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS customer (
105 105 CREATE TABLE IF NOT EXISTS dashboard (
106 106 id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
107 107 configuration varchar(10000000),
108   - customer_id varchar(31),
  108 + assigned_customers varchar(1000000),
109 109 search_text varchar(255),
110 110 tenant_id varchar(31),
111 111 title varchar(255)
... ...
... ... @@ -29,13 +29,17 @@ import org.thingsboard.server.common.data.id.CustomerId;
29 29 import org.thingsboard.server.common.data.id.TenantId;
30 30 import org.thingsboard.server.common.data.page.TextPageData;
31 31 import org.thingsboard.server.common.data.page.TextPageLink;
  32 +import org.thingsboard.server.common.data.page.TimePageData;
  33 +import org.thingsboard.server.common.data.page.TimePageLink;
32 34 import org.thingsboard.server.dao.exception.DataValidationException;
33 35 import org.thingsboard.server.dao.model.ModelConstants;
34 36
35 37 import java.io.IOException;
  38 +import java.sql.Time;
36 39 import java.util.ArrayList;
37 40 import java.util.Collections;
38 41 import java.util.List;
  42 +import java.util.concurrent.ExecutionException;
39 43
40 44 public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
41 45
... ... @@ -68,8 +72,6 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
68 72 Assert.assertNotNull(savedDashboard.getId());
69 73 Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
70 74 Assert.assertEquals(dashboard.getTenantId(), savedDashboard.getTenantId());
71   - Assert.assertNotNull(savedDashboard.getCustomerId());
72   - Assert.assertEquals(ModelConstants.NULL_UUID, savedDashboard.getCustomerId().getId());
73 75 Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
74 76
75 77 savedDashboard.setTitle("My new dashboard");
... ... @@ -280,7 +282,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
280 282 }
281 283
282 284 @Test
283   - public void testFindDashboardsByTenantIdAndCustomerId() {
  285 + public void testFindDashboardsByTenantIdAndCustomerId() throws ExecutionException, InterruptedException {
284 286 Tenant tenant = new Tenant();
285 287 tenant.setTitle("Test tenant");
286 288 tenant = tenantService.saveTenant(tenant);
... ... @@ -303,10 +305,10 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
303 305 }
304 306
305 307 List<DashboardInfo> loadedDashboards = new ArrayList<>();
306   - TextPageLink pageLink = new TextPageLink(23);
307   - TextPageData<DashboardInfo> pageData = null;
  308 + TimePageLink pageLink = new TimePageLink(23);
  309 + TimePageData<DashboardInfo> pageData = null;
308 310 do {
309   - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
  311 + pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
310 312 loadedDashboards.addAll(pageData.getData());
311 313 if (pageData.hasNext()) {
312 314 pageLink = pageData.getNextPageLink();
... ... @@ -320,96 +322,12 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
320 322
321 323 dashboardService.unassignCustomerDashboards(tenantId, customerId);
322 324
323   - pageLink = new TextPageLink(42);
324   - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
  325 + pageLink = new TimePageLink(42);
  326 + pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
325 327 Assert.assertFalse(pageData.hasNext());
326 328 Assert.assertTrue(pageData.getData().isEmpty());
327 329
328 330 tenantService.deleteTenant(tenantId);
329 331 }
330   -
331   - @Test
332   - public void testFindDashboardsByTenantIdCustomerIdAndTitle() {
333   -
334   - Customer customer = new Customer();
335   - customer.setTitle("Test customer");
336   - customer.setTenantId(tenantId);
337   - customer = customerService.saveCustomer(customer);
338   - CustomerId customerId = customer.getId();
339   -
340   - String title1 = "Dashboard title 1";
341   - List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
342   - for (int i=0;i<124;i++) {
343   - Dashboard dashboard = new Dashboard();
344   - dashboard.setTenantId(tenantId);
345   - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
346   - String title = title1+suffix;
347   - title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
348   - dashboard.setTitle(title);
349   - dashboard = dashboardService.saveDashboard(dashboard);
350   - dashboardsTitle1.add(new DashboardInfo(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)));
351   - }
352   - String title2 = "Dashboard title 2";
353   - List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
354   - for (int i=0;i<151;i++) {
355   - Dashboard dashboard = new Dashboard();
356   - dashboard.setTenantId(tenantId);
357   - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
358   - String title = title2+suffix;
359   - title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
360   - dashboard.setTitle(title);
361   - dashboard = dashboardService.saveDashboard(dashboard);
362   - dashboardsTitle2.add(new DashboardInfo(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)));
363   - }
364   -
365   - List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
366   - TextPageLink pageLink = new TextPageLink(24, title1);
367   - TextPageData<DashboardInfo> pageData = null;
368   - do {
369   - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
370   - loadedDashboardsTitle1.addAll(pageData.getData());
371   - if (pageData.hasNext()) {
372   - pageLink = pageData.getNextPageLink();
373   - }
374   - } while (pageData.hasNext());
375   -
376   - Collections.sort(dashboardsTitle1, idComparator);
377   - Collections.sort(loadedDashboardsTitle1, idComparator);
378   -
379   - Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
380   -
381   - List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
382   - pageLink = new TextPageLink(4, title2);
383   - do {
384   - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
385   - loadedDashboardsTitle2.addAll(pageData.getData());
386   - if (pageData.hasNext()) {
387   - pageLink = pageData.getNextPageLink();
388   - }
389   - } while (pageData.hasNext());
390 332
391   - Collections.sort(dashboardsTitle2, idComparator);
392   - Collections.sort(loadedDashboardsTitle2, idComparator);
393   -
394   - Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
395   -
396   - for (DashboardInfo dashboard : loadedDashboardsTitle1) {
397   - dashboardService.deleteDashboard(dashboard.getId());
398   - }
399   -
400   - pageLink = new TextPageLink(4, title1);
401   - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
402   - Assert.assertFalse(pageData.hasNext());
403   - Assert.assertEquals(0, pageData.getData().size());
404   -
405   - for (DashboardInfo dashboard : loadedDashboardsTitle2) {
406   - dashboardService.deleteDashboard(dashboard.getId());
407   - }
408   -
409   - pageLink = new TextPageLink(4, title2);
410   - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
411   - Assert.assertFalse(pageData.hasNext());
412   - Assert.assertEquals(0, pageData.getData().size());
413   - customerService.deleteCustomer(customerId);
414   - }
415 333 }
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.dao.sql.dashboard;
17 17
18 18 import com.datastax.driver.core.utils.UUIDs;
  19 +import org.junit.Assert;
19 20 import org.junit.Test;
20 21 import org.springframework.beans.factory.annotation.Autowired;
21 22 import org.thingsboard.server.common.data.DashboardInfo;
... ... @@ -40,53 +41,26 @@ public class JpaDashboardInfoDaoTest extends AbstractJpaDaoTest {
40 41 @Test
41 42 public void testFindDashboardsByTenantId() {
42 43 UUID tenantId1 = UUIDs.timeBased();
43   - UUID customerId1 = UUIDs.timeBased();
44 44 UUID tenantId2 = UUIDs.timeBased();
45   - UUID customerId2 = UUIDs.timeBased();
46 45
47 46 for (int i = 0; i < 20; i++) {
48   - createDashboard(tenantId1, customerId1, i);
49   - createDashboard(tenantId2, customerId2, i * 2);
  47 + createDashboard(tenantId1, i);
  48 + createDashboard(tenantId2, i * 2);
50 49 }
51 50
52 51 TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD");
53 52 List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink1);
54   - assertEquals(15, dashboardInfos1.size());
  53 + Assert.assertEquals(15, dashboardInfos1.size());
55 54
56 55 TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null);
57 56 List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink2);
58   - assertEquals(5, dashboardInfos2.size());
  57 + Assert.assertEquals(5, dashboardInfos2.size());
59 58 }
60 59
61   - @Test
62   - public void testFindDashboardsByTenantAndCustomerId() {
63   - UUID tenantId1 = UUIDs.timeBased();
64   - UUID customerId1 = UUIDs.timeBased();
65   - UUID tenantId2 = UUIDs.timeBased();
66   - UUID customerId2 = UUIDs.timeBased();
67   -
68   - for (int i = 0; i < 20; i++) {
69   - createDashboard(tenantId1, customerId1, i);
70   - createDashboard(tenantId2, customerId2, i * 2);
71   - }
72   -
73   - TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD");
74   - List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink1);
75   - assertEquals(15, dashboardInfos1.size());
76   -
77   - TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null);
78   - List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink2);
79   - assertEquals(5, dashboardInfos2.size());
80   - }
81   -
82   - private void assertEquals(int i, int size) {
83   - }
84   -
85   - private void createDashboard(UUID tenantId, UUID customerId, int index) {
  60 + private void createDashboard(UUID tenantId, int index) {
86 61 DashboardInfo dashboardInfo = new DashboardInfo();
87 62 dashboardInfo.setId(new DashboardId(UUIDs.timeBased()));
88 63 dashboardInfo.setTenantId(new TenantId(tenantId));
89   - dashboardInfo.setCustomerId(new CustomerId(customerId));
90 64 dashboardInfo.setTitle("DASHBOARD_" + index);
91 65 dashboardInfoDao.save(dashboardInfo);
92 66 }
... ...