Commit ca6ea7dd6dfad805821be8df9ea56ae29b8646b0

Authored by Igor Kulikov
Committed by GitHub
2 parents ea33ce15 d6a3fff8

Merge pull request #619 from thingsboard/feature/customer-dashboards

Feature/customer dashboards
Showing 58 changed files with 1629 additions and 817 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);
... ... @@ -434,28 +434,23 @@ public abstract class BaseController {
434 434 try {
435 435 validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
436 436 DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId);
437   - SecurityUser authUser = getCurrentUser();
438   - checkDashboard(dashboardInfo, authUser.getAuthority() != Authority.SYS_ADMIN);
  437 + checkDashboard(dashboardInfo);
439 438 return dashboardInfo;
440 439 } catch (Exception e) {
441 440 throw handleException(e, false);
442 441 }
443 442 }
444 443
445   - private void checkDashboard(DashboardInfo dashboard, boolean checkCustomerId) throws ThingsboardException {
  444 + private void checkDashboard(DashboardInfo dashboard) throws ThingsboardException {
446 445 checkNotNull(dashboard);
447 446 checkTenantId(dashboard.getTenantId());
448 447 SecurityUser authUser = getCurrentUser();
449 448 if (authUser.getAuthority() == Authority.CUSTOMER_USER) {
450   - if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
  449 + if (!dashboard.isAssignedToCustomer(authUser.getCustomerId())) {
451 450 throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
452 451 ThingsboardErrorCode.PERMISSION_DENIED);
453 452 }
454 453 }
455   - if (checkCustomerId &&
456   - dashboard.getCustomerId() != null && !dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
457   - checkCustomerId(dashboard.getCustomerId());
458   - }
459 454 }
460 455
461 456 ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException {
... ...
... ... @@ -18,20 +18,22 @@ package org.thingsboard.server.controller;
18 18 import org.springframework.http.HttpStatus;
19 19 import org.springframework.security.access.prepost.PreAuthorize;
20 20 import org.springframework.web.bind.annotation.*;
21   -import org.thingsboard.server.common.data.Customer;
22   -import org.thingsboard.server.common.data.Dashboard;
23   -import org.thingsboard.server.common.data.DashboardInfo;
24   -import org.thingsboard.server.common.data.EntityType;
  21 +import org.thingsboard.server.common.data.*;
25 22 import org.thingsboard.server.common.data.audit.ActionType;
26 23 import org.thingsboard.server.common.data.id.CustomerId;
27 24 import org.thingsboard.server.common.data.id.DashboardId;
28 25 import org.thingsboard.server.common.data.id.TenantId;
29 26 import org.thingsboard.server.common.data.page.TextPageData;
30 27 import org.thingsboard.server.common.data.page.TextPageLink;
  28 +import org.thingsboard.server.common.data.page.TimePageData;
  29 +import org.thingsboard.server.common.data.page.TimePageLink;
31 30 import org.thingsboard.server.dao.exception.IncorrectParameterException;
32 31 import org.thingsboard.server.dao.model.ModelConstants;
33 32 import org.thingsboard.server.exception.ThingsboardException;
34 33
  34 +import java.util.HashSet;
  35 +import java.util.Set;
  36 +
35 37 @RestController
36 38 @RequestMapping("/api")
37 39 public class DashboardController extends BaseController {
... ... @@ -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 166
164   - Customer customer = checkCustomerId(dashboard.getCustomerId());
165   -
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;
... ... @@ -181,6 +182,158 @@ public class DashboardController extends BaseController {
181 182 }
182 183
183 184 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  185 + @RequestMapping(value = "/dashboard/{dashboardId}/customers", method = RequestMethod.POST)
  186 + @ResponseBody
  187 + public Dashboard updateDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
  188 + @RequestBody String[] strCustomerIds) throws ThingsboardException {
  189 + checkParameter(DASHBOARD_ID, strDashboardId);
  190 + try {
  191 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  192 + Dashboard dashboard = checkDashboardId(dashboardId);
  193 +
  194 + Set<CustomerId> customerIds = new HashSet<>();
  195 + if (strCustomerIds != null) {
  196 + for (String strCustomerId : strCustomerIds) {
  197 + customerIds.add(new CustomerId(toUUID(strCustomerId)));
  198 + }
  199 + }
  200 +
  201 + Set<CustomerId> addedCustomerIds = new HashSet<>();
  202 + Set<CustomerId> removedCustomerIds = new HashSet<>();
  203 + for (CustomerId customerId : customerIds) {
  204 + if (!dashboard.isAssignedToCustomer(customerId)) {
  205 + addedCustomerIds.add(customerId);
  206 + }
  207 + }
  208 +
  209 + Set<ShortCustomerInfo> assignedCustomers = dashboard.getAssignedCustomers();
  210 + if (assignedCustomers != null) {
  211 + for (ShortCustomerInfo customerInfo : assignedCustomers) {
  212 + if (!customerIds.contains(customerInfo.getCustomerId())) {
  213 + removedCustomerIds.add(customerInfo.getCustomerId());
  214 + }
  215 + }
  216 + }
  217 +
  218 + if (addedCustomerIds.isEmpty() && removedCustomerIds.isEmpty()) {
  219 + return dashboard;
  220 + } else {
  221 + Dashboard savedDashboard = null;
  222 + for (CustomerId customerId : addedCustomerIds) {
  223 + savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
  224 + ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId);
  225 + logEntityAction(dashboardId, savedDashboard,
  226 + customerId,
  227 + ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
  228 + }
  229 + for (CustomerId customerId : removedCustomerIds) {
  230 + ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
  231 + savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
  232 + logEntityAction(dashboardId, dashboard,
  233 + customerId,
  234 + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
  235 +
  236 + }
  237 + return savedDashboard;
  238 + }
  239 + } catch (Exception e) {
  240 +
  241 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  242 + null,
  243 + ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId);
  244 +
  245 + throw handleException(e);
  246 + }
  247 + }
  248 +
  249 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  250 + @RequestMapping(value = "/dashboard/{dashboardId}/customers/add", method = RequestMethod.POST)
  251 + @ResponseBody
  252 + public Dashboard addDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
  253 + @RequestBody String[] strCustomerIds) throws ThingsboardException {
  254 + checkParameter(DASHBOARD_ID, strDashboardId);
  255 + try {
  256 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  257 + Dashboard dashboard = checkDashboardId(dashboardId);
  258 +
  259 + Set<CustomerId> customerIds = new HashSet<>();
  260 + if (strCustomerIds != null) {
  261 + for (String strCustomerId : strCustomerIds) {
  262 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  263 + if (!dashboard.isAssignedToCustomer(customerId)) {
  264 + customerIds.add(customerId);
  265 + }
  266 + }
  267 + }
  268 +
  269 + if (customerIds.isEmpty()) {
  270 + return dashboard;
  271 + } else {
  272 + Dashboard savedDashboard = null;
  273 + for (CustomerId customerId : customerIds) {
  274 + savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
  275 + ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId);
  276 + logEntityAction(dashboardId, savedDashboard,
  277 + customerId,
  278 + ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
  279 + }
  280 + return savedDashboard;
  281 + }
  282 + } catch (Exception e) {
  283 +
  284 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  285 + null,
  286 + ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId);
  287 +
  288 + throw handleException(e);
  289 + }
  290 + }
  291 +
  292 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  293 + @RequestMapping(value = "/dashboard/{dashboardId}/customers/remove", method = RequestMethod.POST)
  294 + @ResponseBody
  295 + public Dashboard removeDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
  296 + @RequestBody String[] strCustomerIds) throws ThingsboardException {
  297 + checkParameter(DASHBOARD_ID, strDashboardId);
  298 + try {
  299 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  300 + Dashboard dashboard = checkDashboardId(dashboardId);
  301 +
  302 + Set<CustomerId> customerIds = new HashSet<>();
  303 + if (strCustomerIds != null) {
  304 + for (String strCustomerId : strCustomerIds) {
  305 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  306 + if (dashboard.isAssignedToCustomer(customerId)) {
  307 + customerIds.add(customerId);
  308 + }
  309 + }
  310 + }
  311 +
  312 + if (customerIds.isEmpty()) {
  313 + return dashboard;
  314 + } else {
  315 + Dashboard savedDashboard = null;
  316 + for (CustomerId customerId : customerIds) {
  317 + ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
  318 + savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
  319 + logEntityAction(dashboardId, dashboard,
  320 + customerId,
  321 + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
  322 +
  323 + }
  324 + return savedDashboard;
  325 + }
  326 + } catch (Exception e) {
  327 +
  328 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  329 + null,
  330 + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
  331 +
  332 + throw handleException(e);
  333 + }
  334 + }
  335 +
  336 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
184 337 @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST)
185 338 @ResponseBody
186 339 public Dashboard assignDashboardToPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
... ... @@ -192,7 +345,7 @@ public class DashboardController extends BaseController {
192 345 Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId()));
193 346
194 347 logEntityAction(dashboardId, savedDashboard,
195   - savedDashboard.getCustomerId(),
  348 + publicCustomer.getId(),
196 349 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
197 350
198 351 return savedDashboard;
... ... @@ -206,6 +359,33 @@ public class DashboardController extends BaseController {
206 359 }
207 360 }
208 361
  362 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  363 + @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE)
  364 + @ResponseBody
  365 + public Dashboard unassignDashboardFromPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
  366 + checkParameter(DASHBOARD_ID, strDashboardId);
  367 + try {
  368 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  369 + Dashboard dashboard = checkDashboardId(dashboardId);
  370 + Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId());
  371 +
  372 + Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, publicCustomer.getId()));
  373 +
  374 + logEntityAction(dashboardId, dashboard,
  375 + publicCustomer.getId(),
  376 + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
  377 +
  378 + return savedDashboard;
  379 + } catch (Exception e) {
  380 +
  381 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  382 + null,
  383 + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
  384 +
  385 + throw handleException(e);
  386 + }
  387 + }
  388 +
209 389 @PreAuthorize("hasAuthority('SYS_ADMIN')")
210 390 @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
211 391 @ResponseBody
... ... @@ -245,19 +425,20 @@ public class DashboardController extends BaseController {
245 425 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
246 426 @RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
247 427 @ResponseBody
248   - public TextPageData<DashboardInfo> getCustomerDashboards(
  428 + public TimePageData<DashboardInfo> getCustomerDashboards(
249 429 @PathVariable("customerId") String strCustomerId,
250 430 @RequestParam int limit,
251   - @RequestParam(required = false) String textSearch,
252   - @RequestParam(required = false) String idOffset,
253   - @RequestParam(required = false) String textOffset) throws ThingsboardException {
  431 + @RequestParam(required = false) Long startTime,
  432 + @RequestParam(required = false) Long endTime,
  433 + @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
  434 + @RequestParam(required = false) String offset) throws ThingsboardException {
254 435 checkParameter("customerId", strCustomerId);
255 436 try {
256 437 TenantId tenantId = getCurrentUser().getTenantId();
257 438 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
258 439 checkCustomerId(customerId);
259   - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
260   - return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
  440 + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
  441 + return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get());
261 442 } catch (Exception e) {
262 443 throw handleException(e);
263 444 }
... ...
... ... @@ -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", true);
  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, true);
  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.JavaType;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.apache.commons.csv.CSVFormat;
  22 +import org.apache.commons.csv.CSVParser;
  23 +import org.apache.commons.lang3.StringUtils;
  24 +import org.thingsboard.server.common.data.ShortCustomerInfo;
  25 +import org.thingsboard.server.common.data.UUIDConverter;
  26 +import org.thingsboard.server.common.data.id.CustomerId;
  27 +import org.thingsboard.server.common.data.id.DashboardId;
  28 +import org.thingsboard.server.dao.dashboard.DashboardService;
  29 +
  30 +import java.io.IOException;
  31 +import java.nio.file.Files;
  32 +import java.nio.file.Path;
  33 +import java.util.*;
  34 +
  35 +/**
  36 + * Created by igor on 2/27/18.
  37 + */
  38 +@Slf4j
  39 +public class DatabaseHelper {
  40 +
  41 + public static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
  42 +
  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 + public static final String DASHBOARD = "dashboard";
  50 + public static final String ID = "id";
  51 + public static final String TITLE = "title";
  52 + public static final String ASSIGNED_CUSTOMERS = "assigned_customers";
  53 + public static final String CONFIGURATION = "configuration";
  54 +
  55 + public static final ObjectMapper objectMapper = new ObjectMapper();
  56 +
  57 + public static void upgradeTo40_assignDashboards(Path dashboardsDump, DashboardService dashboardService, boolean sql) throws Exception {
  58 + JavaType assignedCustomersType =
  59 + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
  60 + try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(dashboardsDump), CSV_DUMP_FORMAT.withFirstRecordAsHeader())) {
  61 + csvParser.forEach(record -> {
  62 + String customerIdString = record.get(CUSTOMER_ID);
  63 + String assignedCustomersString = record.get(ASSIGNED_CUSTOMERS);
  64 + DashboardId dashboardId = new DashboardId(toUUID(record.get(ID), sql));
  65 + List<CustomerId> customerIds = new ArrayList<>();
  66 + if (!StringUtils.isEmpty(assignedCustomersString)) {
  67 + try {
  68 + Set<ShortCustomerInfo> assignedCustomers = objectMapper.readValue(assignedCustomersString, assignedCustomersType);
  69 + assignedCustomers.forEach((customerInfo) -> {
  70 + CustomerId customerId = customerInfo.getCustomerId();
  71 + if (!customerId.isNullUid()) {
  72 + customerIds.add(customerId);
  73 + }
  74 + });
  75 + } catch (IOException e) {
  76 + log.error("Unable to parse assigned customers field", e);
  77 + }
  78 + }
  79 + if (!StringUtils.isEmpty(customerIdString)) {
  80 + CustomerId customerId = new CustomerId(toUUID(customerIdString, sql));
  81 + if (!customerId.isNullUid()) {
  82 + customerIds.add(customerId);
  83 + }
  84 + }
  85 + for (CustomerId customerId : customerIds) {
  86 + dashboardService.assignDashboardToCustomer(dashboardId, customerId);
  87 + }
  88 + });
  89 + }
  90 + }
  91 +
  92 + private static UUID toUUID(String src, boolean sql) {
  93 + if (sql) {
  94 + return UUIDConverter.fromString(src);
  95 + } else {
  96 + return UUID.fromString(src);
  97 + }
  98 + }
  99 +
  100 +}
... ...
... ... @@ -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", true);
  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, true);
  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);
... ...
... ... @@ -28,16 +28,25 @@ import java.nio.file.Path;
28 28 import java.nio.file.StandardCopyOption;
29 29 import java.util.*;
30 30
31   -public class CassandraDbHelper {
  31 +import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT;
32 32
33   - private static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
  33 +public class CassandraDbHelper {
34 34
35 35 public static Path dumpCfIfExists(KeyspaceMetadata ks, Session session, String cfName,
36 36 String[] columns, String[] defaultValues, String dumpPrefix) throws Exception {
  37 + return dumpCfIfExists(ks, session, cfName, columns, defaultValues, dumpPrefix, false);
  38 + }
  39 +
  40 + public static Path dumpCfIfExists(KeyspaceMetadata ks, Session session, String cfName,
  41 + String[] columns, String[] defaultValues, String dumpPrefix, boolean printHeader) throws Exception {
37 42 if (ks.getTable(cfName) != null) {
38 43 Path dumpFile = Files.createTempFile(dumpPrefix, null);
39 44 Files.deleteIfExists(dumpFile);
40   - try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), CSV_DUMP_FORMAT)) {
  45 + CSVFormat csvFormat = CSV_DUMP_FORMAT;
  46 + if (printHeader) {
  47 + csvFormat = csvFormat.withHeader(columns);
  48 + }
  49 + try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), csvFormat)) {
41 50 Statement stmt = new SimpleStatement("SELECT * FROM " + cfName);
42 51 stmt.setFetchSize(1000);
43 52 ResultSet rs = session.execute(stmt);
... ... @@ -75,9 +84,19 @@ public class CassandraDbHelper {
75 84 }
76 85
77 86 public static void loadCf(KeyspaceMetadata ks, Session session, String cfName, String[] columns, Path sourceFile) throws Exception {
  87 + loadCf(ks, session, cfName, columns, sourceFile, false);
  88 + }
  89 +
  90 + public static void loadCf(KeyspaceMetadata ks, Session session, String cfName, String[] columns, Path sourceFile, boolean parseHeader) throws Exception {
78 91 TableMetadata tableMetadata = ks.getTable(cfName);
79 92 PreparedStatement prepared = session.prepare(createInsertStatement(cfName, columns));
80   - try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), CSV_DUMP_FORMAT.withHeader(columns))) {
  93 + CSVFormat csvFormat = CSV_DUMP_FORMAT;
  94 + if (parseHeader) {
  95 + csvFormat = csvFormat.withFirstRecordAsHeader();
  96 + } else {
  97 + csvFormat = CSV_DUMP_FORMAT.withHeader(columns);
  98 + }
  99 + try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), csvFormat)) {
81 100 csvParser.forEach(record -> {
82 101 BoundStatement boundStatement = prepared.bind();
83 102 for (String column : columns) {
... ...
  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.CSVFormat;
  20 +import org.apache.commons.csv.CSVParser;
  21 +import org.apache.commons.csv.CSVPrinter;
  22 +import org.apache.commons.csv.CSVRecord;
  23 +
  24 +import java.nio.file.Files;
  25 +import java.nio.file.Path;
  26 +import java.sql.*;
  27 +import java.util.ArrayList;
  28 +import java.util.HashMap;
  29 +import java.util.List;
  30 +import java.util.Map;
  31 +
  32 +import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT;
  33 +
  34 +/**
  35 + * Created by igor on 2/27/18.
  36 + */
  37 +@Slf4j
  38 +public class SqlDbHelper {
  39 +
  40 + public static Path dumpTableIfExists(Connection conn, String tableName,
  41 + String[] columns, String[] defaultValues, String dumpPrefix) throws Exception {
  42 + return dumpTableIfExists(conn, tableName, columns, defaultValues, dumpPrefix, false);
  43 + }
  44 +
  45 + public static Path dumpTableIfExists(Connection conn, String tableName,
  46 + String[] columns, String[] defaultValues, String dumpPrefix, boolean printHeader) throws Exception {
  47 +
  48 + if (tableExists(conn, tableName)) {
  49 + Path dumpFile = Files.createTempFile(dumpPrefix, null);
  50 + Files.deleteIfExists(dumpFile);
  51 + CSVFormat csvFormat = CSV_DUMP_FORMAT;
  52 + if (printHeader) {
  53 + csvFormat = csvFormat.withHeader(columns);
  54 + }
  55 + try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), csvFormat)) {
  56 + try (PreparedStatement stmt = conn.prepareStatement("SELECT * FROM " + tableName)) {
  57 + try (ResultSet tableRes = stmt.executeQuery()) {
  58 + ResultSetMetaData resMetaData = tableRes.getMetaData();
  59 + Map<String, Integer> columnIndexMap = new HashMap<>();
  60 + for (int i = 1; i <= resMetaData.getColumnCount(); i++) {
  61 + String columnName = resMetaData.getColumnName(i);
  62 + columnIndexMap.put(columnName.toUpperCase(), i);
  63 + }
  64 + while(tableRes.next()) {
  65 + dumpRow(tableRes, columnIndexMap, columns, defaultValues, csvPrinter);
  66 + }
  67 + }
  68 + }
  69 + }
  70 + return dumpFile;
  71 + } else {
  72 + return null;
  73 + }
  74 + }
  75 +
  76 + private static boolean tableExists(Connection conn, String tableName) {
  77 + try (Statement stmt = conn.createStatement()) {
  78 + stmt.executeQuery("select * from " + tableName + " where 1=0");
  79 + return true;
  80 + } catch (Exception e) {
  81 + return false;
  82 + }
  83 + }
  84 +
  85 + public static void loadTable(Connection conn, String tableName, String[] columns, Path sourceFile) throws Exception {
  86 + loadTable(conn, tableName, columns, sourceFile, false);
  87 + }
  88 +
  89 + public static void loadTable(Connection conn, String tableName, String[] columns, Path sourceFile, boolean parseHeader) throws Exception {
  90 + CSVFormat csvFormat = CSV_DUMP_FORMAT;
  91 + if (parseHeader) {
  92 + csvFormat = csvFormat.withFirstRecordAsHeader();
  93 + } else {
  94 + csvFormat = CSV_DUMP_FORMAT.withHeader(columns);
  95 + }
  96 + try (PreparedStatement prepared = conn.prepareStatement(createInsertStatement(tableName, columns))) {
  97 + try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), csvFormat)) {
  98 + csvParser.forEach(record -> {
  99 + try {
  100 + for (int i = 0; i < columns.length; i++) {
  101 + setColumnValue(i, columns[i], record, prepared);
  102 + }
  103 + prepared.execute();
  104 + } catch (SQLException e) {
  105 + log.error("Unable to load table record!", e);
  106 + }
  107 + });
  108 + }
  109 + }
  110 + }
  111 +
  112 + private static void dumpRow(ResultSet res, Map<String, Integer> columnIndexMap, String[] columns,
  113 + String[] defaultValues, CSVPrinter csvPrinter) throws Exception {
  114 + List<String> record = new ArrayList<>();
  115 + for (int i=0;i<columns.length;i++) {
  116 + String column = columns[i];
  117 + String defaultValue;
  118 + if (defaultValues != null && i < defaultValues.length) {
  119 + defaultValue = defaultValues[i];
  120 + } else {
  121 + defaultValue = "";
  122 + }
  123 + record.add(getColumnValue(column, defaultValue, columnIndexMap, res));
  124 + }
  125 + csvPrinter.printRecord(record);
  126 + }
  127 +
  128 + private static String getColumnValue(String column, String defaultValue, Map<String, Integer> columnIndexMap, ResultSet res) {
  129 + int index = columnIndexMap.containsKey(column.toUpperCase()) ? columnIndexMap.get(column.toUpperCase()) : -1;
  130 + if (index > -1) {
  131 + String str;
  132 + try {
  133 + Object obj = res.getObject(index);
  134 + if (obj == null) {
  135 + return null;
  136 + } else {
  137 + str = obj.toString();
  138 + }
  139 + } catch (Exception e) {
  140 + str = "";
  141 + }
  142 + return str;
  143 + } else {
  144 + return defaultValue;
  145 + }
  146 + }
  147 +
  148 + private static void setColumnValue(int index, String column,
  149 + CSVRecord record, PreparedStatement preparedStatement) throws SQLException {
  150 + String value = record.get(column);
  151 + int type = preparedStatement.getParameterMetaData().getParameterType(index + 1);
  152 + preparedStatement.setObject(index + 1, value, type);
  153 + }
  154 +
  155 + private static String createInsertStatement(String tableName, String[] columns) {
  156 + StringBuilder insertStatementBuilder = new StringBuilder();
  157 + insertStatementBuilder.append("INSERT INTO ").append(tableName).append(" (");
  158 + for (String column : columns) {
  159 + insertStatementBuilder.append(column).append(",");
  160 + }
  161 + insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
  162 + insertStatementBuilder.append(") VALUES (");
  163 + for (String column : columns) {
  164 + insertStatementBuilder.append("?").append(",");
  165 + }
  166 + insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
  167 + insertStatementBuilder.append(")");
  168 + return insertStatementBuilder.toString();
  169 + }
  170 +
  171 +}
... ...
... ... @@ -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().contains(savedCustomer.toShortCustomerInfo()));
  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().contains(savedCustomer.toShortCustomerInfo()));
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 }
... ...
... ... @@ -69,6 +69,11 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
69 69 return false;
70 70 }
71 71
  72 + @JsonIgnore
  73 + public ShortCustomerInfo toShortCustomerInfo() {
  74 + return new ShortCustomerInfo(id, title, isPublic());
  75 + }
  76 +
72 77 @Override
73 78 @JsonProperty(access = Access.READ_ONLY)
74 79 public String getName() {
... ...
... ... @@ -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,13 @@ 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.*;
  24 +
23 25 public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName {
24 26
25 27 private TenantId tenantId;
26   - private CustomerId customerId;
27 28 private String title;
  29 + private Set<ShortCustomerInfo> assignedCustomers;
28 30
29 31 public DashboardInfo() {
30 32 super();
... ... @@ -37,8 +39,8 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
37 39 public DashboardInfo(DashboardInfo dashboardInfo) {
38 40 super(dashboardInfo);
39 41 this.tenantId = dashboardInfo.getTenantId();
40   - this.customerId = dashboardInfo.getCustomerId();
41 42 this.title = dashboardInfo.getTitle();
  43 + this.assignedCustomers = dashboardInfo.getAssignedCustomers();
42 44 }
43 45
44 46 public TenantId getTenantId() {
... ... @@ -49,14 +51,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
49 51 this.tenantId = tenantId;
50 52 }
51 53
52   - public CustomerId getCustomerId() {
53   - return customerId;
54   - }
55   -
56   - public void setCustomerId(CustomerId customerId) {
57   - this.customerId = customerId;
58   - }
59   -
60 54 public String getTitle() {
61 55 return title;
62 56 }
... ... @@ -65,6 +59,62 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
65 59 this.title = title;
66 60 }
67 61
  62 + public Set<ShortCustomerInfo> getAssignedCustomers() {
  63 + return assignedCustomers;
  64 + }
  65 +
  66 + public void setAssignedCustomers(Set<ShortCustomerInfo> assignedCustomers) {
  67 + this.assignedCustomers = assignedCustomers;
  68 + }
  69 +
  70 + public boolean isAssignedToCustomer(CustomerId customerId) {
  71 + return this.assignedCustomers != null && this.assignedCustomers.contains(new ShortCustomerInfo(customerId, null, false));
  72 + }
  73 +
  74 + public ShortCustomerInfo getAssignedCustomerInfo(CustomerId customerId) {
  75 + if (this.assignedCustomers != null) {
  76 + for (ShortCustomerInfo customerInfo : this.assignedCustomers) {
  77 + if (customerInfo.getCustomerId().equals(customerId)) {
  78 + return customerInfo;
  79 + }
  80 + }
  81 + }
  82 + return null;
  83 + }
  84 +
  85 + public boolean addAssignedCustomer(Customer customer) {
  86 + ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
  87 + if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
  88 + return false;
  89 + } else {
  90 + if (this.assignedCustomers == null) {
  91 + this.assignedCustomers = new HashSet<>();
  92 + }
  93 + this.assignedCustomers.add(customerInfo);
  94 + return true;
  95 + }
  96 + }
  97 +
  98 + public boolean updateAssignedCustomer(Customer customer) {
  99 + ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
  100 + if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
  101 + this.assignedCustomers.add(customerInfo);
  102 + return true;
  103 + } else {
  104 + return false;
  105 + }
  106 + }
  107 +
  108 + public boolean removeAssignedCustomer(Customer customer) {
  109 + ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
  110 + if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
  111 + this.assignedCustomers.remove(customerInfo);
  112 + return true;
  113 + } else {
  114 + return false;
  115 + }
  116 + }
  117 +
68 118 @Override
69 119 @JsonProperty(access = JsonProperty.Access.READ_ONLY)
70 120 public String getName() {
... ... @@ -80,7 +130,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
80 130 public int hashCode() {
81 131 final int prime = 31;
82 132 int result = super.hashCode();
83   - result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
84 133 result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
85 134 result = prime * result + ((title == null) ? 0 : title.hashCode());
86 135 return result;
... ... @@ -95,11 +144,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
95 144 if (getClass() != obj.getClass())
96 145 return false;
97 146 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 147 if (tenantId == null) {
104 148 if (other.tenantId != null)
105 149 return false;
... ... @@ -118,8 +162,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
118 162 StringBuilder builder = new StringBuilder();
119 163 builder.append("DashboardInfo [tenantId=");
120 164 builder.append(tenantId);
121   - builder.append(", customerId=");
122   - builder.append(customerId);
123 165 builder.append(", title=");
124 166 builder.append(title);
125 167 builder.append("]");
... ...
  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.common.data;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Getter;
  20 +import lombok.Setter;
  21 +import org.thingsboard.server.common.data.id.CustomerId;
  22 +
  23 +/**
  24 + * Created by igor on 2/27/18.
  25 + */
  26 +
  27 +@AllArgsConstructor
  28 +public class ShortCustomerInfo {
  29 +
  30 + @Getter @Setter
  31 + private CustomerId customerId;
  32 +
  33 + @Getter @Setter
  34 + private String title;
  35 +
  36 + @Getter @Setter
  37 + private boolean isPublic;
  38 +
  39 + @Override
  40 + public boolean equals(Object o) {
  41 + if (this == o) return true;
  42 + if (o == null || getClass() != o.getClass()) return false;
  43 +
  44 + ShortCustomerInfo that = (ShortCustomerInfo) o;
  45 +
  46 + return customerId.equals(that.customerId);
  47 +
  48 + }
  49 +
  50 + @Override
  51 + public int hashCode() {
  52 + return customerId.hashCode();
  53 + }
  54 +}
... ...
... ... @@ -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.getId());
  102 + return savedCustomer;
101 103 }
102 104
103 105 @Override
... ... @@ -108,7 +110,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
108 110 if (customer == null) {
109 111 throw new IncorrectParameterException("Unable to delete non-existent customer.");
110 112 }
111   - dashboardService.unassignCustomerDashboards(customer.getTenantId(), customerId);
  113 + dashboardService.unassignCustomerDashboards(customerId);
112 114 assetService.unassignCustomerAssets(customer.getTenantId(), customerId);
113 115 deviceService.unassignCustomerDevices(customer.getTenantId(), customerId);
114 116 userService.deleteCustomerUsers(customer.getTenantId(), customerId);
... ...
... ... @@ -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.util.Set;
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);
  54 +
  55 + void unassignCustomerDashboards(CustomerId customerId);
50 56
51   - void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId);
  57 + void updateCustomerDashboards(CustomerId customerId);
52 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,63 @@ 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(customer)) {
  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 + Customer customer = customerDao.findById(customerId.getId());
  137 + if (customer == null) {
  138 + throw new DataValidationException("Can't unassign dashboard from non-existent customer!");
  139 + }
  140 + if (dashboard.removeAssignedCustomer(customer)) {
  141 + try {
  142 + deleteRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
  143 + } catch (ExecutionException | InterruptedException e) {
  144 + log.warn("[{}] Failed to delete dashboard relation. Customer Id: [{}]", dashboardId, customerId);
  145 + throw new RuntimeException(e);
  146 + }
  147 + return saveDashboard(dashboard);
  148 + } else {
  149 + return dashboard;
  150 + }
  151 + }
  152 +
  153 + private Dashboard updateAssignedCustomer(DashboardId dashboardId, Customer customer) {
  154 + Dashboard dashboard = findDashboardById(dashboardId);
  155 + if (dashboard.updateAssignedCustomer(customer)) {
  156 + return saveDashboard(dashboard);
  157 + } else {
  158 + return dashboard;
  159 + }
  160 + }
  161 +
  162 + private void deleteRelation(EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
  163 + log.debug("Deleting Dashboard relation: {}", dashboardRelation);
  164 + relationService.deleteRelationAsync(dashboardRelation).get();
  165 + }
  166 +
  167 + private void createRelation(EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
  168 + log.debug("Creating Dashboard relation: {}", dashboardRelation);
  169 + relationService.saveRelationAsync(dashboardRelation).get();
110 170 }
111 171
112 172 @Override
... ... @@ -134,23 +194,44 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
134 194 }
135 195
136 196 @Override
137   - public TextPageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) {
  197 + public ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) {
138 198 log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink);
139 199 Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
140 200 Validator.validateId(customerId, "Incorrect customerId " + customerId);
141 201 Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
142   - List<DashboardInfo> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
143   - return new TextPageData<>(dashboards, pageLink);
  202 + ListenableFuture<List<DashboardInfo>> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
  203 +
  204 + return Futures.transform(dashboards, new Function<List<DashboardInfo>, TimePageData<DashboardInfo>>() {
  205 + @Nullable
  206 + @Override
  207 + public TimePageData<DashboardInfo> apply(@Nullable List<DashboardInfo> dashboards) {
  208 + return new TimePageData<>(dashboards, pageLink);
  209 + }
  210 + });
144 211 }
145 212
146 213 @Override
147   - public void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId) {
148   - log.trace("Executing unassignCustomerDashboards, tenantId [{}], customerId [{}]", tenantId, customerId);
149   - Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  214 + public void unassignCustomerDashboards(CustomerId customerId) {
  215 + log.trace("Executing unassignCustomerDashboards, customerId [{}]", customerId);
150 216 Validator.validateId(customerId, "Incorrect customerId " + customerId);
151   - new CustomerDashboardsUnassigner(tenantId).removeEntities(customerId);
  217 + Customer customer = customerDao.findById(customerId.getId());
  218 + if (customer == null) {
  219 + throw new DataValidationException("Can't unassign dashboards from non-existent customer!");
  220 + }
  221 + new CustomerDashboardsUnassigner(customer).removeEntities(customer);
152 222 }
153   -
  223 +
  224 + @Override
  225 + public void updateCustomerDashboards(CustomerId customerId) {
  226 + log.trace("Executing updateCustomerDashboards, customerId [{}]", customerId);
  227 + Validator.validateId(customerId, "Incorrect customerId " + customerId);
  228 + Customer customer = customerDao.findById(customerId.getId());
  229 + if (customer == null) {
  230 + throw new DataValidationException("Can't update dashboards for non-existent customer!");
  231 + }
  232 + new CustomerDashboardsUpdater(customer).removeEntities(customer);
  233 + }
  234 +
154 235 private DataValidator<Dashboard> dashboardValidator =
155 236 new DataValidator<Dashboard>() {
156 237 @Override
... ... @@ -166,17 +247,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
166 247 throw new DataValidationException("Dashboard is referencing to non-existent tenant!");
167 248 }
168 249 }
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 250 }
181 251 };
182 252
... ... @@ -194,24 +264,54 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
194 264 }
195 265 };
196 266
197   - private class CustomerDashboardsUnassigner extends PaginatedRemover<CustomerId, DashboardInfo> {
198   -
199   - private TenantId tenantId;
  267 + private class CustomerDashboardsUnassigner extends TimePaginatedRemover<Customer, DashboardInfo> {
200 268
201   - CustomerDashboardsUnassigner(TenantId tenantId) {
202   - this.tenantId = tenantId;
  269 + private Customer customer;
  270 +
  271 + CustomerDashboardsUnassigner(Customer customer) {
  272 + this.customer = customer;
203 273 }
204 274
205 275 @Override
206   - protected List<DashboardInfo> findEntities(CustomerId id, TextPageLink pageLink) {
207   - return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
  276 + protected List<DashboardInfo> findEntities(Customer customer, TimePageLink pageLink) {
  277 + try {
  278 + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get();
  279 + } catch (InterruptedException | ExecutionException e) {
  280 + log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", customer.getTenantId().getId(), customer.getId().getId());
  281 + throw new RuntimeException(e);
  282 + }
208 283 }
209 284
210 285 @Override
211 286 protected void removeEntity(DashboardInfo entity) {
212   - unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()));
  287 + unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()), this.customer.getId());
213 288 }
214 289
215 290 }
216 291
  292 + private class CustomerDashboardsUpdater extends TimePaginatedRemover<Customer, DashboardInfo> {
  293 +
  294 + private Customer customer;
  295 +
  296 + CustomerDashboardsUpdater(Customer customer) {
  297 + this.customer = customer;
  298 + }
  299 +
  300 + @Override
  301 + protected List<DashboardInfo> findEntities(Customer customer, TimePageLink pageLink) {
  302 + try {
  303 + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get();
  304 + } catch (InterruptedException | ExecutionException e) {
  305 + log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", customer.getTenantId().getId(), customer.getId().getId());
  306 + throw new RuntimeException(e);
  307 + }
  308 + }
  309 +
  310 + @Override
  311 + protected void removeEntity(DashboardInfo entity) {
  312 + updateAssignedCustomer(new DashboardId(entity.getUuidId()), this.customer);
  313 + }
  314 +
  315 + }
  316 +
217 317 }
... ...
... ... @@ -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,23 @@ 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.JavaType;
22 24 import com.fasterxml.jackson.databind.JsonNode;
  25 +import com.fasterxml.jackson.databind.ObjectMapper;
23 26 import lombok.EqualsAndHashCode;
24 27 import lombok.ToString;
  28 +import lombok.extern.slf4j.Slf4j;
  29 +import org.springframework.util.StringUtils;
25 30 import org.thingsboard.server.common.data.Dashboard;
26   -import org.thingsboard.server.common.data.id.CustomerId;
  31 +import org.thingsboard.server.common.data.ShortCustomerInfo;
27 32 import org.thingsboard.server.common.data.id.DashboardId;
28 33 import org.thingsboard.server.common.data.id.TenantId;
29 34 import org.thingsboard.server.dao.model.SearchTextEntity;
30 35 import org.thingsboard.server.dao.model.type.JsonCodec;
31 36
  37 +import java.io.IOException;
  38 +import java.util.HashSet;
32 39 import java.util.UUID;
33 40
34 41 import static org.thingsboard.server.dao.model.ModelConstants.*;
... ... @@ -36,8 +43,13 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
36 43 @Table(name = DASHBOARD_COLUMN_FAMILY_NAME)
37 44 @EqualsAndHashCode
38 45 @ToString
  46 +@Slf4j
39 47 public final class DashboardEntity implements SearchTextEntity<Dashboard> {
40   -
  48 +
  49 + private static final ObjectMapper objectMapper = new ObjectMapper();
  50 + private static final JavaType assignedCustomersType =
  51 + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
  52 +
41 53 @PartitionKey(value = 0)
42 54 @Column(name = ID_PROPERTY)
43 55 private UUID id;
... ... @@ -46,16 +58,15 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
46 58 @Column(name = DASHBOARD_TENANT_ID_PROPERTY)
47 59 private UUID tenantId;
48 60
49   - @PartitionKey(value = 2)
50   - @Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)
51   - private UUID customerId;
52   -
53 61 @Column(name = DASHBOARD_TITLE_PROPERTY)
54 62 private String title;
55 63
56 64 @Column(name = SEARCH_TEXT_PROPERTY)
57 65 private String searchText;
58   -
  66 +
  67 + @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  68 + private String assignedCustomers;
  69 +
59 70 @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
60 71 private JsonNode configuration;
61 72
... ... @@ -70,10 +81,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
70 81 if (dashboard.getTenantId() != null) {
71 82 this.tenantId = dashboard.getTenantId().getId();
72 83 }
73   - if (dashboard.getCustomerId() != null) {
74   - this.customerId = dashboard.getCustomerId().getId();
75   - }
76 84 this.title = dashboard.getTitle();
  85 + if (dashboard.getAssignedCustomers() != null) {
  86 + try {
  87 + this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers());
  88 + } catch (JsonProcessingException e) {
  89 + log.error("Unable to serialize assigned customers to string!", e);
  90 + }
  91 + }
77 92 this.configuration = dashboard.getConfiguration();
78 93 }
79 94
... ... @@ -93,14 +108,6 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
93 108 this.tenantId = tenantId;
94 109 }
95 110
96   - public UUID getCustomerId() {
97   - return customerId;
98   - }
99   -
100   - public void setCustomerId(UUID customerId) {
101   - this.customerId = customerId;
102   - }
103   -
104 111 public String getTitle() {
105 112 return title;
106 113 }
... ... @@ -109,6 +116,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
109 116 this.title = title;
110 117 }
111 118
  119 + public String getAssignedCustomers() {
  120 + return assignedCustomers;
  121 + }
  122 +
  123 + public void setAssignedCustomers(String assignedCustomers) {
  124 + this.assignedCustomers = assignedCustomers;
  125 + }
  126 +
112 127 public JsonNode getConfiguration() {
113 128 return configuration;
114 129 }
... ... @@ -138,10 +153,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
138 153 if (tenantId != null) {
139 154 dashboard.setTenantId(new TenantId(tenantId));
140 155 }
141   - if (customerId != null) {
142   - dashboard.setCustomerId(new CustomerId(customerId));
143   - }
144 156 dashboard.setTitle(title);
  157 + if (!StringUtils.isEmpty(assignedCustomers)) {
  158 + try {
  159 + dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
  160 + } catch (IOException e) {
  161 + log.warn("Unable to parse assigned customers!", e);
  162 + }
  163 + }
145 164 dashboard.setConfiguration(configuration);
146 165 return dashboard;
147 166 }
... ...
... ... @@ -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.JavaType;
  24 +import com.fasterxml.jackson.databind.ObjectMapper;
22 25 import lombok.EqualsAndHashCode;
23 26 import lombok.ToString;
  27 +import lombok.extern.slf4j.Slf4j;
  28 +import org.springframework.util.StringUtils;
24 29 import org.thingsboard.server.common.data.DashboardInfo;
25   -import org.thingsboard.server.common.data.id.CustomerId;
  30 +import org.thingsboard.server.common.data.ShortCustomerInfo;
26 31 import org.thingsboard.server.common.data.id.DashboardId;
27 32 import org.thingsboard.server.common.data.id.TenantId;
28 33 import org.thingsboard.server.dao.model.SearchTextEntity;
29 34
  35 +import java.io.IOException;
  36 +import java.util.HashSet;
30 37 import java.util.UUID;
31 38
32 39 import static org.thingsboard.server.dao.model.ModelConstants.*;
... ... @@ -34,8 +41,13 @@ 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 + private static final JavaType assignedCustomersType =
  49 + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
  50 +
39 51 @PartitionKey(value = 0)
40 52 @Column(name = ID_PROPERTY)
41 53 private UUID id;
... ... @@ -44,16 +56,15 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
44 56 @Column(name = DASHBOARD_TENANT_ID_PROPERTY)
45 57 private UUID tenantId;
46 58
47   - @PartitionKey(value = 2)
48   - @Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)
49   - private UUID customerId;
50   -
51 59 @Column(name = DASHBOARD_TITLE_PROPERTY)
52 60 private String title;
53 61
54 62 @Column(name = SEARCH_TEXT_PROPERTY)
55 63 private String searchText;
56 64
  65 + @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  66 + private String assignedCustomers;
  67 +
57 68 public DashboardInfoEntity() {
58 69 super();
59 70 }
... ... @@ -65,10 +76,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
65 76 if (dashboardInfo.getTenantId() != null) {
66 77 this.tenantId = dashboardInfo.getTenantId().getId();
67 78 }
68   - if (dashboardInfo.getCustomerId() != null) {
69   - this.customerId = dashboardInfo.getCustomerId().getId();
70   - }
71 79 this.title = dashboardInfo.getTitle();
  80 + if (dashboardInfo.getAssignedCustomers() != null) {
  81 + try {
  82 + this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers());
  83 + } catch (JsonProcessingException e) {
  84 + log.error("Unable to serialize assigned customers to string!", e);
  85 + }
  86 + }
72 87 }
73 88
74 89 public UUID getId() {
... ... @@ -87,14 +102,6 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
87 102 this.tenantId = tenantId;
88 103 }
89 104
90   - public UUID getCustomerId() {
91   - return customerId;
92   - }
93   -
94   - public void setCustomerId(UUID customerId) {
95   - this.customerId = customerId;
96   - }
97   -
98 105 public String getTitle() {
99 106 return title;
100 107 }
... ... @@ -103,6 +110,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
103 110 this.title = title;
104 111 }
105 112
  113 + public String getAssignedCustomers() {
  114 + return assignedCustomers;
  115 + }
  116 +
  117 + public void setAssignedCustomers(String assignedCustomers) {
  118 + this.assignedCustomers = assignedCustomers;
  119 + }
  120 +
106 121 @Override
107 122 public String getSearchTextSource() {
108 123 return getTitle();
... ... @@ -124,10 +139,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
124 139 if (tenantId != null) {
125 140 dashboardInfo.setTenantId(new TenantId(tenantId));
126 141 }
127   - if (customerId != null) {
128   - dashboardInfo.setCustomerId(new CustomerId(customerId));
129   - }
130 142 dashboardInfo.setTitle(title);
  143 + if (!StringUtils.isEmpty(assignedCustomers)) {
  144 + try {
  145 + dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
  146 + } catch (IOException e) {
  147 + log.warn("Unable to parse assigned customers!", e);
  148 + }
  149 + }
131 150 return dashboardInfo;
132 151 }
133 152
... ...
... ... @@ -16,13 +16,18 @@
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.JavaType;
19 21 import com.fasterxml.jackson.databind.JsonNode;
  22 +import com.fasterxml.jackson.databind.ObjectMapper;
20 23 import lombok.Data;
21 24 import lombok.EqualsAndHashCode;
  25 +import lombok.extern.slf4j.Slf4j;
22 26 import org.hibernate.annotations.Type;
23 27 import org.hibernate.annotations.TypeDef;
  28 +import org.springframework.util.StringUtils;
24 29 import org.thingsboard.server.common.data.Dashboard;
25   -import org.thingsboard.server.common.data.id.CustomerId;
  30 +import org.thingsboard.server.common.data.ShortCustomerInfo;
26 31 import org.thingsboard.server.common.data.id.DashboardId;
27 32 import org.thingsboard.server.common.data.id.TenantId;
28 33 import org.thingsboard.server.dao.model.BaseSqlEntity;
... ... @@ -33,26 +38,33 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType;
33 38 import javax.persistence.Column;
34 39 import javax.persistence.Entity;
35 40 import javax.persistence.Table;
  41 +import java.io.IOException;
  42 +import java.util.HashSet;
36 43
37 44 @Data
  45 +@Slf4j
38 46 @EqualsAndHashCode(callSuper = true)
39 47 @Entity
40 48 @TypeDef(name = "json", typeClass = JsonStringType.class)
41 49 @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
42 50 public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements SearchTextEntity<Dashboard> {
43 51
  52 + private static final ObjectMapper objectMapper = new ObjectMapper();
  53 + private static final JavaType assignedCustomersType =
  54 + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
  55 +
44 56 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
45 57 private String tenantId;
46 58
47   - @Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
48   - private String customerId;
49   -
50 59 @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
51 60 private String title;
52 61
53 62 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
54 63 private String searchText;
55 64
  65 + @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  66 + private String assignedCustomers;
  67 +
56 68 @Type(type = "json")
57 69 @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY)
58 70 private JsonNode configuration;
... ... @@ -68,10 +80,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
68 80 if (dashboard.getTenantId() != null) {
69 81 this.tenantId = toString(dashboard.getTenantId().getId());
70 82 }
71   - if (dashboard.getCustomerId() != null) {
72   - this.customerId = toString(dashboard.getCustomerId().getId());
73   - }
74 83 this.title = dashboard.getTitle();
  84 + if (dashboard.getAssignedCustomers() != null) {
  85 + try {
  86 + this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers());
  87 + } catch (JsonProcessingException e) {
  88 + log.error("Unable to serialize assigned customers to string!", e);
  89 + }
  90 + }
75 91 this.configuration = dashboard.getConfiguration();
76 92 }
77 93
... ... @@ -92,10 +108,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
92 108 if (tenantId != null) {
93 109 dashboard.setTenantId(new TenantId(toUUID(tenantId)));
94 110 }
95   - if (customerId != null) {
96   - dashboard.setCustomerId(new CustomerId(toUUID(customerId)));
97   - }
98 111 dashboard.setTitle(title);
  112 + if (!StringUtils.isEmpty(assignedCustomers)) {
  113 + try {
  114 + dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
  115 + } catch (IOException e) {
  116 + log.warn("Unable to parse assigned customers!", e);
  117 + }
  118 + }
99 119 dashboard.setConfiguration(configuration);
100 120 return dashboard;
101 121 }
... ...
... ... @@ -16,10 +16,15 @@
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.JavaType;
  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.springframework.util.StringUtils;
21 26 import org.thingsboard.server.common.data.DashboardInfo;
22   -import org.thingsboard.server.common.data.id.CustomerId;
  27 +import org.thingsboard.server.common.data.ShortCustomerInfo;
23 28 import org.thingsboard.server.common.data.id.DashboardId;
24 29 import org.thingsboard.server.common.data.id.TenantId;
25 30 import org.thingsboard.server.dao.model.BaseSqlEntity;
... ... @@ -29,25 +34,32 @@ 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.io.IOException;
  38 +import java.util.HashSet;
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 + private static final JavaType assignedCustomersType =
  49 + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
  50 +
39 51 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
40 52 private String tenantId;
41 53
42   - @Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
43   - private String customerId;
44   -
45 54 @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
46 55 private String title;
47 56
48 57 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
49 58 private String searchText;
50 59
  60 + @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  61 + private String assignedCustomers;
  62 +
51 63 public DashboardInfoEntity() {
52 64 super();
53 65 }
... ... @@ -59,10 +71,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
59 71 if (dashboardInfo.getTenantId() != null) {
60 72 this.tenantId = toString(dashboardInfo.getTenantId().getId());
61 73 }
62   - if (dashboardInfo.getCustomerId() != null) {
63   - this.customerId = toString(dashboardInfo.getCustomerId().getId());
64   - }
65 74 this.title = dashboardInfo.getTitle();
  75 + if (dashboardInfo.getAssignedCustomers() != null) {
  76 + try {
  77 + this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers());
  78 + } catch (JsonProcessingException e) {
  79 + log.error("Unable to serialize assigned customers to string!", e);
  80 + }
  81 + }
66 82 }
67 83
68 84 @Override
... ... @@ -86,10 +102,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
86 102 if (tenantId != null) {
87 103 dashboardInfo.setTenantId(new TenantId(toUUID(tenantId)));
88 104 }
89   - if (customerId != null) {
90   - dashboardInfo.setCustomerId(new CustomerId(toUUID(customerId)));
91   - }
92 105 dashboardInfo.setTitle(title);
  106 + if (!StringUtils.isEmpty(assignedCustomers)) {
  107 + try {
  108 + dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
  109 + } catch (IOException e) {
  110 + log.warn("Unable to parse assigned customers!", e);
  111 + }
  112 + }
93 113 return dashboardInfo;
94 114 }
95 115
... ...
  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();
... ... @@ -318,98 +320,14 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
318 320
319 321 Assert.assertEquals(dashboards, loadedDashboards);
320 322
321   - dashboardService.unassignCustomerDashboards(tenantId, customerId);
  323 + dashboardService.unassignCustomerDashboards(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 }
... ...
... ... @@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.dashboard', [])
17 17 .factory('dashboardService', DashboardService).name;
18 18
19 19 /*@ngInject*/
20   -function DashboardService($rootScope, $http, $q, $location, customerService) {
  20 +function DashboardService($rootScope, $http, $q, $location, $filter) {
21 21
22 22 var stDiffPromise;
23 23
... ... @@ -37,7 +37,11 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
37 37 deleteDashboard: deleteDashboard,
38 38 saveDashboard: saveDashboard,
39 39 unassignDashboardFromCustomer: unassignDashboardFromCustomer,
  40 + updateDashboardCustomers: updateDashboardCustomers,
  41 + addDashboardCustomers: addDashboardCustomers,
  42 + removeDashboardCustomers: removeDashboardCustomers,
40 43 makeDashboardPublic: makeDashboardPublic,
  44 + makeDashboardPrivate: makeDashboardPrivate,
41 45 getPublicDashboardLink: getPublicDashboardLink
42 46 }
43 47
... ... @@ -56,14 +60,14 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
56 60 url += '&textOffset=' + pageLink.textOffset;
57 61 }
58 62 $http.get(url, config).then(function success(response) {
59   - deferred.resolve(response.data);
  63 + deferred.resolve(prepareDashboards(response.data));
60 64 }, function fail() {
61 65 deferred.reject();
62 66 });
63 67 return deferred.promise;
64 68 }
65 69
66   - function getTenantDashboards(pageLink, applyCustomersInfo, config) {
  70 + function getTenantDashboards(pageLink, config) {
67 71 var deferred = $q.defer();
68 72 var url = '/api/tenant/dashboards?limit=' + pageLink.limit;
69 73 if (angular.isDefined(pageLink.textSearch)) {
... ... @@ -76,51 +80,25 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
76 80 url += '&textOffset=' + pageLink.textOffset;
77 81 }
78 82 $http.get(url, config).then(function success(response) {
79   - if (applyCustomersInfo) {
80   - customerService.applyAssignedCustomersInfo(response.data.data).then(
81   - function success(data) {
82   - response.data.data = data;
83   - deferred.resolve(response.data);
84   - },
85   - function fail() {
86   - deferred.reject();
87   - }
88   - );
89   - } else {
90   - deferred.resolve(response.data);
91   - }
  83 + deferred.resolve(prepareDashboards(response.data));
92 84 }, function fail() {
93 85 deferred.reject();
94 86 });
95 87 return deferred.promise;
96 88 }
97 89
98   - function getCustomerDashboards(customerId, pageLink, applyCustomersInfo, config) {
  90 + function getCustomerDashboards(customerId, pageLink, config) {
99 91 var deferred = $q.defer();
100 92 var url = '/api/customer/' + customerId + '/dashboards?limit=' + pageLink.limit;
101   - if (angular.isDefined(pageLink.textSearch)) {
102   - url += '&textSearch=' + pageLink.textSearch;
103   - }
104 93 if (angular.isDefined(pageLink.idOffset)) {
105   - url += '&idOffset=' + pageLink.idOffset;
106   - }
107   - if (angular.isDefined(pageLink.textOffset)) {
108   - url += '&textOffset=' + pageLink.textOffset;
  94 + url += '&offset=' + pageLink.idOffset;
109 95 }
110 96 $http.get(url, config).then(function success(response) {
111   - if (applyCustomersInfo) {
112   - customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
113   - function success(data) {
114   - response.data.data = data;
115   - deferred.resolve(response.data);
116   - },
117   - function fail() {
118   - deferred.reject();
119   - }
120   - );
121   - } else {
122   - deferred.resolve(response.data);
  97 + response.data = prepareDashboards(response.data);
  98 + if (pageLink.textSearch) {
  99 + response.data.data = $filter('filter')(response.data.data, {title: pageLink.textSearch});
123 100 }
  101 + deferred.resolve(response.data);
124 102 }, function fail() {
125 103 deferred.reject();
126 104 });
... ... @@ -151,7 +129,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
151 129 var deferred = $q.defer();
152 130 var url = '/api/dashboard/' + dashboardId;
153 131 $http.get(url, null).then(function success(response) {
154   - deferred.resolve(response.data);
  132 + deferred.resolve(prepareDashboard(response.data));
155 133 }, function fail() {
156 134 deferred.reject();
157 135 });
... ... @@ -162,7 +140,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
162 140 var deferred = $q.defer();
163 141 var url = '/api/dashboard/info/' + dashboardId;
164 142 $http.get(url, config).then(function success(response) {
165   - deferred.resolve(response.data);
  143 + deferred.resolve(prepareDashboard(response.data));
166 144 }, function fail() {
167 145 deferred.reject();
168 146 });
... ... @@ -172,8 +150,8 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
172 150 function saveDashboard(dashboard) {
173 151 var deferred = $q.defer();
174 152 var url = '/api/dashboard';
175   - $http.post(url, dashboard).then(function success(response) {
176   - deferred.resolve(response.data);
  153 + $http.post(url, cleanDashboard(dashboard)).then(function success(response) {
  154 + deferred.resolve(prepareDashboard(response.data));
177 155 }, function fail() {
178 156 deferred.reject();
179 157 });
... ... @@ -195,18 +173,51 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
195 173 var deferred = $q.defer();
196 174 var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
197 175 $http.post(url, null).then(function success(response) {
198   - deferred.resolve(response.data);
  176 + deferred.resolve(prepareDashboard(response.data));
199 177 }, function fail() {
200 178 deferred.reject();
201 179 });
202 180 return deferred.promise;
203 181 }
204 182
205   - function unassignDashboardFromCustomer(dashboardId) {
  183 + function unassignDashboardFromCustomer(customerId, dashboardId) {
206 184 var deferred = $q.defer();
207   - var url = '/api/customer/dashboard/' + dashboardId;
  185 + var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
208 186 $http.delete(url).then(function success(response) {
209   - deferred.resolve(response.data);
  187 + deferred.resolve(prepareDashboard(response.data));
  188 + }, function fail() {
  189 + deferred.reject();
  190 + });
  191 + return deferred.promise;
  192 + }
  193 +
  194 + function updateDashboardCustomers(dashboardId, customerIds) {
  195 + var deferred = $q.defer();
  196 + var url = '/api/dashboard/' + dashboardId + '/customers';
  197 + $http.post(url, customerIds).then(function success(response) {
  198 + deferred.resolve(prepareDashboard(response.data));
  199 + }, function fail() {
  200 + deferred.reject();
  201 + });
  202 + return deferred.promise;
  203 + }
  204 +
  205 + function addDashboardCustomers(dashboardId, customerIds) {
  206 + var deferred = $q.defer();
  207 + var url = '/api/dashboard/' + dashboardId + '/customers/add';
  208 + $http.post(url, customerIds).then(function success(response) {
  209 + deferred.resolve(prepareDashboard(response.data));
  210 + }, function fail() {
  211 + deferred.reject();
  212 + });
  213 + return deferred.promise;
  214 + }
  215 +
  216 + function removeDashboardCustomers(dashboardId, customerIds) {
  217 + var deferred = $q.defer();
  218 + var url = '/api/dashboard/' + dashboardId + '/customers/remove';
  219 + $http.post(url, customerIds).then(function success(response) {
  220 + deferred.resolve(prepareDashboard(response.data));
210 221 }, function fail() {
211 222 deferred.reject();
212 223 });
... ... @@ -217,7 +228,18 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
217 228 var deferred = $q.defer();
218 229 var url = '/api/customer/public/dashboard/' + dashboardId;
219 230 $http.post(url, null).then(function success(response) {
220   - deferred.resolve(response.data);
  231 + deferred.resolve(prepareDashboard(response.data));
  232 + }, function fail() {
  233 + deferred.reject();
  234 + });
  235 + return deferred.promise;
  236 + }
  237 +
  238 + function makeDashboardPrivate(dashboardId) {
  239 + var deferred = $q.defer();
  240 + var url = '/api/customer/public/dashboard/' + dashboardId;
  241 + $http.delete(url).then(function success(response) {
  242 + deferred.resolve(prepareDashboard(response.data));
221 243 }, function fail() {
222 244 deferred.reject();
223 245 });
... ... @@ -230,8 +252,44 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
230 252 if (port != 80 && port != 443) {
231 253 url += ":" + port;
232 254 }
233   - url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.customerId.id;
  255 + url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.publicCustomerId;
234 256 return url;
235 257 }
236 258
  259 + function prepareDashboards(dashboardsData) {
  260 + if (dashboardsData.data) {
  261 + for (var i = 0; i < dashboardsData.data.length; i++) {
  262 + dashboardsData.data[i] = prepareDashboard(dashboardsData.data[i]);
  263 + }
  264 + }
  265 + return dashboardsData;
  266 + }
  267 +
  268 + function prepareDashboard(dashboard) {
  269 + dashboard.publicCustomerId = null;
  270 + dashboard.assignedCustomersText = "";
  271 + dashboard.assignedCustomersIds = [];
  272 + if (dashboard.assignedCustomers && dashboard.assignedCustomers.length) {
  273 + var assignedCustomersTitles = [];
  274 + for (var i = 0; i < dashboard.assignedCustomers.length; i++) {
  275 + var assignedCustomer = dashboard.assignedCustomers[i];
  276 + dashboard.assignedCustomersIds.push(assignedCustomer.customerId.id);
  277 + if (assignedCustomer.public) {
  278 + dashboard.publicCustomerId = assignedCustomer.customerId.id;
  279 + } else {
  280 + assignedCustomersTitles.push(assignedCustomer.title);
  281 + }
  282 + }
  283 + dashboard.assignedCustomersText = assignedCustomersTitles.join(', ');
  284 + }
  285 + return dashboard;
  286 + }
  287 +
  288 + function cleanDashboard(dashboard) {
  289 + delete dashboard.publicCustomerId;
  290 + delete dashboard.assignedCustomersText;
  291 + delete dashboard.assignedCustomersIds;
  292 + return dashboard;
  293 + }
  294 +
237 295 }
... ...
... ... @@ -273,9 +273,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
273 273 break;
274 274 case types.entityType.dashboard:
275 275 if (user.authority === 'CUSTOMER_USER') {
276   - promise = dashboardService.getCustomerDashboards(customerId, pageLink, false, config);
  276 + promise = dashboardService.getCustomerDashboards(customerId, pageLink, config);
277 277 } else {
278   - promise = dashboardService.getTenantDashboards(pageLink, false, config);
  278 + promise = dashboardService.getTenantDashboards(pageLink, config);
279 279 }
280 280 break;
281 281 case types.entityType.user:
... ... @@ -403,6 +403,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
403 403 return deferred.promise;
404 404 }
405 405
  406 + function resolveAliasEntityId(entityType, id) {
  407 + var entityId = {
  408 + entityType: entityType,
  409 + id: id
  410 + };
  411 + if (entityType == types.aliasEntityType.current_customer) {
  412 + var user = userService.getCurrentUser();
  413 + entityId.entityType = types.entityType.customer;
  414 + if (user.authority === 'CUSTOMER_USER') {
  415 + entityId.id = user.customerId;
  416 + }
  417 + }
  418 + return entityId;
  419 + }
  420 +
406 421 function getStateEntityId(filter, stateParams) {
407 422 var entityId = null;
408 423 if (stateParams) {
... ... @@ -417,6 +432,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
417 432 if (!entityId) {
418 433 entityId = filter.defaultStateEntity;
419 434 }
  435 + if (entityId) {
  436 + entityId = resolveAliasEntityId(entityId.entityType, entityId.id);
  437 + }
420 438 return entityId;
421 439 }
422 440
... ... @@ -432,7 +450,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
432 450 var stateEntityId = getStateEntityId(filter, stateParams);
433 451 switch (filter.type) {
434 452 case types.aliasFilterType.singleEntity.value:
435   - getEntity(filter.singleEntity.entityType, filter.singleEntity.id, {ignoreLoading: true}).then(
  453 + var aliasEntityId = resolveAliasEntityId(filter.singleEntity.entityType, filter.singleEntity.id);
  454 + getEntity(aliasEntityId.entityType, aliasEntityId.id, {ignoreLoading: true}).then(
436 455 function success(entity) {
437 456 result.entities = entitiesToEntitiesInfo([entity]);
438 457 deferred.resolve(result);
... ... @@ -530,10 +549,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
530 549 rootEntityId = filter.rootEntity.id;
531 550 }
532 551 if (rootEntityType && rootEntityId) {
  552 + var relationQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
533 553 var searchQuery = {
534 554 parameters: {
535   - rootId: rootEntityId,
536   - rootType: rootEntityType,
  555 + rootId: relationQueryRootEntityId.id,
  556 + rootType: relationQueryRootEntityId.entityType,
537 557 direction: filter.direction
538 558 },
539 559 filters: filter.filters
... ... @@ -571,10 +591,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
571 591 rootEntityId = filter.rootEntity.id;
572 592 }
573 593 if (rootEntityType && rootEntityId) {
  594 + var searchQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
574 595 searchQuery = {
575 596 parameters: {
576   - rootId: rootEntityId,
577   - rootType: rootEntityType,
  597 + rootId: searchQueryRootEntityId.id,
  598 + rootType: searchQueryRootEntityId.entityType,
578 599 direction: filter.direction
579 600 },
580 601 relationType: filter.relationType
... ... @@ -709,7 +730,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
709 730 return result;
710 731 }
711 732
712   - function prepareAllowedEntityTypesList(allowedEntityTypes) {
  733 + function prepareAllowedEntityTypesList(allowedEntityTypes, useAliasEntityTypes) {
713 734 var authority = userService.getAuthority();
714 735 var entityTypes = {};
715 736 switch(authority) {
... ... @@ -726,12 +747,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
726 747 entityTypes.rule = types.entityType.rule;
727 748 entityTypes.plugin = types.entityType.plugin;
728 749 entityTypes.dashboard = types.entityType.dashboard;
  750 + if (useAliasEntityTypes) {
  751 + entityTypes.current_customer = types.aliasEntityType.current_customer;
  752 + }
729 753 break;
730 754 case 'CUSTOMER_USER':
731 755 entityTypes.device = types.entityType.device;
732 756 entityTypes.asset = types.entityType.asset;
733 757 entityTypes.customer = types.entityType.customer;
734 758 entityTypes.dashboard = types.entityType.dashboard;
  759 + if (useAliasEntityTypes) {
  760 + entityTypes.current_customer = types.aliasEntityType.current_customer;
  761 + }
735 762 break;
736 763 }
737 764
... ...
... ... @@ -266,9 +266,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
266 266 var pageLink = {limit: 100};
267 267 var fetchDashboardsPromise;
268 268 if (currentUser.authority === 'TENANT_ADMIN') {
269   - fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink, false);
  269 + fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink);
270 270 } else {
271   - fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink, false);
  271 + fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink);
272 272 }
273 273 fetchDashboardsPromise.then(
274 274 function success(result) {
... ...
... ... @@ -296,6 +296,9 @@ export default angular.module('thingsboard.types', [])
296 296 dashboard: "DASHBOARD",
297 297 alarm: "ALARM"
298 298 },
  299 + aliasEntityType: {
  300 + current_customer: "CURRENT_CUSTOMER"
  301 + },
299 302 entityTypeTranslations: {
300 303 "DEVICE": {
301 304 type: 'entity.type-device',
... ... @@ -350,6 +353,10 @@ export default angular.module('thingsboard.types', [])
350 353 typePlural: 'entity.type-alarms',
351 354 list: 'entity.list-of-alarms',
352 355 nameStartsWith: 'entity.alarm-name-starts-with'
  356 + },
  357 + "CURRENT_CUSTOMER": {
  358 + type: 'entity.type-current-customer',
  359 + list: 'entity.type-current-customer'
353 360 }
354 361 },
355 362 entitySearchDirection: {
... ...
... ... @@ -48,7 +48,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
48 48 var promise;
49 49 if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
50 50 if (scope.customerId) {
51   - promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false, {ignoreLoading: true});
  51 + promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, {ignoreLoading: true});
52 52 } else {
53 53 promise = $q.when({data: []});
54 54 }
... ... @@ -60,7 +60,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
60 60 promise = $q.when({data: []});
61 61 }
62 62 } else {
63   - promise = dashboardService.getTenantDashboards(pageLink, false, {ignoreLoading: true});
  63 + promise = dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true});
64 64 }
65 65 }
66 66
... ...
... ... @@ -48,12 +48,12 @@ function DashboardSelect($compile, $templateCache, $q, $mdMedia, $mdPanel, $docu
48 48 var promise;
49 49 if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
50 50 if (scope.customerId && scope.customerId != types.id.nullUid) {
51   - promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false, {ignoreLoading: true});
  51 + promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, {ignoreLoading: true});
52 52 } else {
53 53 promise = $q.when({data: []});
54 54 }
55 55 } else {
56   - promise = dashboardService.getTenantDashboards(pageLink, false, {ignoreLoading: true});
  56 + promise = dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true});
57 57 }
58 58
59 59 promise.then(function success(result) {
... ...
... ... @@ -52,7 +52,7 @@ export default function AddDashboardsToCustomerController(dashboardService, $mdD
52 52 fetchMoreItems_: function () {
53 53 if (vm.dashboards.hasNext && !vm.dashboards.pending) {
54 54 vm.dashboards.pending = true;
55   - dashboardService.getTenantDashboards(vm.dashboards.nextPageLink, false).then(
  55 + dashboardService.getTenantDashboards(vm.dashboards.nextPageLink).then(
56 56 function success(dashboards) {
57 57 vm.dashboards.data = vm.dashboards.data.concat(dashboards.data);
58 58 vm.dashboards.nextPageLink = dashboards.nextPageLink;
... ...
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   -/*@ngInject*/
17   -export default function AssignDashboardToCustomerController(customerService, dashboardService, $mdDialog, $q, dashboardIds, customers) {
18   -
19   - var vm = this;
20   -
21   - vm.customers = customers;
22   - vm.searchText = '';
23   -
24   - vm.assign = assign;
25   - vm.cancel = cancel;
26   - vm.isCustomerSelected = isCustomerSelected;
27   - vm.hasData = hasData;
28   - vm.noData = noData;
29   - vm.searchCustomerTextUpdated = searchCustomerTextUpdated;
30   - vm.toggleCustomerSelection = toggleCustomerSelection;
31   -
32   - vm.theCustomers = {
33   - getItemAtIndex: function (index) {
34   - if (index > vm.customers.data.length) {
35   - vm.theCustomers.fetchMoreItems_(index);
36   - return null;
37   - }
38   - var item = vm.customers.data[index];
39   - if (item) {
40   - item.indexNumber = index + 1;
41   - }
42   - return item;
43   - },
44   -
45   - getLength: function () {
46   - if (vm.customers.hasNext) {
47   - return vm.customers.data.length + vm.customers.nextPageLink.limit;
48   - } else {
49   - return vm.customers.data.length;
50   - }
51   - },
52   -
53   - fetchMoreItems_: function () {
54   - if (vm.customers.hasNext && !vm.customers.pending) {
55   - vm.customers.pending = true;
56   - customerService.getCustomers(vm.customers.nextPageLink).then(
57   - function success(customers) {
58   - vm.customers.data = vm.customers.data.concat(customers.data);
59   - vm.customers.nextPageLink = customers.nextPageLink;
60   - vm.customers.hasNext = customers.hasNext;
61   - if (vm.customers.hasNext) {
62   - vm.customers.nextPageLink.limit = vm.customers.pageSize;
63   - }
64   - vm.customers.pending = false;
65   - },
66   - function fail() {
67   - vm.customers.hasNext = false;
68   - vm.customers.pending = false;
69   - });
70   - }
71   - }
72   - };
73   -
74   - function cancel () {
75   - $mdDialog.cancel();
76   - }
77   -
78   - function assign () {
79   - var tasks = [];
80   - for (var dashboardId in dashboardIds) {
81   - tasks.push(dashboardService.assignDashboardToCustomer(vm.customers.selection.id.id, dashboardIds[dashboardId]));
82   - }
83   - $q.all(tasks).then(function () {
84   - $mdDialog.hide();
85   - });
86   - }
87   -
88   - function noData () {
89   - return vm.customers.data.length == 0 && !vm.customers.hasNext;
90   - }
91   -
92   - function hasData () {
93   - return vm.customers.data.length > 0;
94   - }
95   -
96   - function toggleCustomerSelection ($event, customer) {
97   - $event.stopPropagation();
98   - if (vm.isCustomerSelected(customer)) {
99   - vm.customers.selection = null;
100   - } else {
101   - vm.customers.selection = customer;
102   - }
103   - }
104   -
105   - function isCustomerSelected (customer) {
106   - return vm.customers.selection != null && customer &&
107   - customer.id.id === vm.customers.selection.id.id;
108   - }
109   -
110   - function searchCustomerTextUpdated () {
111   - vm.customers = {
112   - pageSize: vm.customers.pageSize,
113   - data: [],
114   - nextPageLink: {
115   - limit: vm.customers.pageSize,
116   - textSearch: vm.searchText
117   - },
118   - selection: null,
119   - hasNext: true,
120   - pending: false
121   - };
122   - }
123   -}
  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 +
  17 +.tb-dashboard-assigned-customers {
  18 + display: block;
  19 + display: -webkit-box;
  20 + height: 34px;
  21 + overflow: hidden;
  22 + text-overflow: ellipsis;
  23 + -webkit-line-clamp: 2;
  24 + -webkit-box-orient: vertical;
  25 + margin-bottom: 4px;
  26 +}
  27 +
... ...
... ... @@ -15,6 +15,6 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'dashboard.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
19   -<div class="tb-small" ng-show="vm.isPublic()">{{'dashboard.public' | translate}}</div>
  18 +<div class="tb-small tb-dashboard-assigned-customers" ng-show="vm.parentCtl.dashboardsScope === 'tenant' && vm.item.assignedCustomersText">{{'dashboard.assignedToCustomers' | translate}}: '{{vm.item.assignedCustomersText}}'</div>
  19 +<div class="tb-small" ng-show="vm.parentCtl.dashboardsScope === 'tenant' && vm.item.publicCustomerId">{{'dashboard.public' | translate}}</div>
20 20
... ...
... ... @@ -19,24 +19,29 @@
19 19 ng-show="!isEdit && dashboardScope === 'tenant'"
20 20 class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button>
21 21 <md-button ng-click="onMakePublic({event: $event})"
22   - ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer && !isPublic"
  22 + ng-show="!isEdit && dashboardScope === 'tenant' && !dashboard.publicCustomerId"
23 23 class="md-raised md-primary">{{ 'dashboard.make-public' | translate }}</md-button>
24   -<md-button ng-click="onAssignToCustomer({event: $event})"
25   - ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer"
26   - class="md-raised md-primary">{{ 'dashboard.assign-to-customer' | translate }}</md-button>
27   -<md-button ng-click="onUnassignFromCustomer({event: $event, isPublic: isPublic})"
28   - ng-show="!isEdit && (dashboardScope === 'customer' || dashboardScope === 'tenant') && isAssignedToCustomer"
29   - class="md-raised md-primary">{{ isPublic ? 'dashboard.make-private' : 'dashboard.unassign-from-customer' | translate }}</md-button>
  24 +<md-button ng-click="onMakePrivate({event: $event})"
  25 + ng-show="!isEdit && ((dashboardScope === 'tenant' && dashboard.publicCustomerId ||
  26 + dashboardScope === 'customer' && customerId == dashboard.publicCustomerId))"
  27 + class="md-raised md-primary">{{ 'dashboard.make-private' | translate }}</md-button>
  28 +<md-button ng-click="onManageAssignedCustomers({event: $event})"
  29 + ng-show="!isEdit && dashboardScope === 'tenant'"
  30 + class="md-raised md-primary">{{ 'dashboard.manage-assigned-customers' | translate }}</md-button>
  31 +<md-button ng-click="onUnassignFromCustomer({event: $event})"
  32 + ng-show="!isEdit && dashboardScope === 'customer' && customerId != dashboard.publicCustomerId"
  33 + class="md-raised md-primary">{{ 'dashboard.unassign-from-customer' | translate }}</md-button>
30 34 <md-button ng-click="onDeleteDashboard({event: $event})"
31 35 ng-show="!isEdit && dashboardScope === 'tenant'"
32 36 class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button>
33 37 <md-content class="md-padding" layout="column">
34 38 <md-input-container class="md-block"
35   - ng-show="!isEdit && isAssignedToCustomer && !isPublic && dashboardScope === 'tenant'">
36   - <label translate>dashboard.assignedToCustomer</label>
37   - <input ng-model="assignedCustomer.title" disabled>
  39 + ng-show="!isEdit && dashboard.assignedCustomersText && dashboardScope === 'tenant'">
  40 + <label translate>dashboard.assignedToCustomers</label>
  41 + <input ng-model="dashboard.assignedCustomersText" disabled>
38 42 </md-input-container>
39   - <div layout="column" ng-show="!isEdit && isPublic && (dashboardScope === 'customer' || dashboardScope === 'tenant')">
  43 + <div layout="column" ng-show="!isEdit && ((dashboardScope === 'tenant' && dashboard.publicCustomerId ||
  44 + dashboardScope === 'customer' && customerId == dashboard.publicCustomerId))">
40 45 <tb-social-share-panel style="padding-bottom: 10px;"
41 46 share-title="{{ 'dashboard.socialshare-title' | translate:{dashboardTitle: dashboard.title} }}"
42 47 share-text="{{ 'dashboard.socialshare-text' | translate:{dashboardTitle: dashboard.title} }}"
... ...
... ... @@ -20,36 +20,17 @@ import dashboardFieldsetTemplate from './dashboard-fieldset.tpl.html';
20 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 22 /*@ngInject*/
23   -export default function DashboardDirective($compile, $templateCache, $translate, types, toast, customerService, dashboardService) {
  23 +export default function DashboardDirective($compile, $templateCache, $translate, types, toast, dashboardService) {
24 24 var linker = function (scope, element) {
25 25 var template = $templateCache.get(dashboardFieldsetTemplate);
26 26 element.html(template);
27   -
28   - scope.isAssignedToCustomer = false;
29   - scope.isPublic = false;
30   - scope.assignedCustomer = null;
31 27 scope.publicLink = null;
32   -
33 28 scope.$watch('dashboard', function(newVal) {
34 29 if (newVal) {
35   - if (scope.dashboard.customerId && scope.dashboard.customerId.id !== types.id.nullUid) {
36   - scope.isAssignedToCustomer = true;
37   - customerService.getShortCustomerInfo(scope.dashboard.customerId.id).then(
38   - function success(customer) {
39   - scope.assignedCustomer = customer;
40   - scope.isPublic = customer.isPublic;
41   - if (scope.isPublic) {
42   - scope.publicLink = dashboardService.getPublicDashboardLink(scope.dashboard);
43   - } else {
44   - scope.publicLink = null;
45   - }
46   - }
47   - );
  30 + if (scope.dashboard.publicCustomerId) {
  31 + scope.publicLink = dashboardService.getPublicDashboardLink(scope.dashboard);
48 32 } else {
49   - scope.isAssignedToCustomer = false;
50   - scope.isPublic = false;
51 33 scope.publicLink = null;
52   - scope.assignedCustomer = null;
53 34 }
54 35 }
55 36 });
... ... @@ -66,10 +47,12 @@ export default function DashboardDirective($compile, $templateCache, $translate,
66 47 scope: {
67 48 dashboard: '=',
68 49 isEdit: '=',
  50 + customerId: '=',
69 51 dashboardScope: '=',
70 52 theForm: '=',
71   - onAssignToCustomer: '&',
72 53 onMakePublic: '&',
  54 + onMakePrivate: '&',
  55 + onManageAssignedCustomers: '&',
73 56 onUnassignFromCustomer: '&',
74 57 onExportDashboard: '&',
75 58 onDeleteDashboard: '&'
... ...
... ... @@ -17,12 +17,14 @@
17 17
18 18 import addDashboardTemplate from './add-dashboard.tpl.html';
19 19 import dashboardCard from './dashboard-card.tpl.html';
20   -import assignToCustomerTemplate from './assign-to-customer.tpl.html';
21 20 import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.html';
22 21 import makeDashboardPublicDialogTemplate from './make-dashboard-public-dialog.tpl.html';
  22 +import manageAssignedCustomersTemplate from './manage-assigned-customers.tpl.html';
23 23
24 24 /* eslint-enable import/no-unresolved, import/default */
25 25
  26 +import './dashboard-card.scss';
  27 +
26 28 /*@ngInject*/
27 29 export function MakeDashboardPublicDialogController($mdDialog, $translate, toast, dashboardService, dashboard) {
28 30
... ... @@ -48,23 +50,8 @@ export function MakeDashboardPublicDialogController($mdDialog, $translate, toast
48 50 export function DashboardCardController(types) {
49 51
50 52 var vm = this;
51   -
52 53 vm.types = types;
53 54
54   - vm.isAssignedToCustomer = function() {
55   - if (vm.item && vm.item.customerId && vm.parentCtl.dashboardsScope === 'tenant' &&
56   - vm.item.customerId.id != vm.types.id.nullUid && !vm.item.assignedCustomer.isPublic) {
57   - return true;
58   - }
59   - return false;
60   - }
61   -
62   - vm.isPublic = function() {
63   - if (vm.item && vm.item.assignedCustomer && vm.parentCtl.dashboardsScope === 'tenant' && vm.item.assignedCustomer.isPublic) {
64   - return true;
65   - }
66   - return false;
67   - }
68 55 }
69 56
70 57 /*@ngInject*/
... ... @@ -135,8 +122,9 @@ export function DashboardsController(userService, dashboardService, customerServ
135 122
136 123 vm.dashboardsScope = $state.$current.data.dashboardsType;
137 124
138   - vm.assignToCustomer = assignToCustomer;
139 125 vm.makePublic = makePublic;
  126 + vm.makePrivate = makePrivate;
  127 + vm.manageAssignedCustomers = manageAssignedCustomers;
140 128 vm.unassignFromCustomer = unassignFromCustomer;
141 129 vm.exportDashboard = exportDashboard;
142 130
... ... @@ -155,6 +143,7 @@ export function DashboardsController(userService, dashboardService, customerServ
155 143 }
156 144
157 145 if (customerId) {
  146 + vm.customerId = customerId;
158 147 vm.customerDashboardsTitle = $translate.instant('customer.dashboards');
159 148 customerService.getShortCustomerInfo(customerId).then(
160 149 function success(info) {
... ... @@ -167,7 +156,7 @@ export function DashboardsController(userService, dashboardService, customerServ
167 156
168 157 if (vm.dashboardsScope === 'tenant') {
169 158 fetchDashboardsFunction = function (pageLink) {
170   - return dashboardService.getTenantDashboards(pageLink, true);
  159 + return dashboardService.getTenantDashboards(pageLink);
171 160 };
172 161 deleteDashboardFunction = function (dashboardId) {
173 162 return dashboardService.deleteDashboard(dashboardId);
... ... @@ -194,11 +183,33 @@ export function DashboardsController(userService, dashboardService, customerServ
194 183 details: function() { return $translate.instant('dashboard.make-public') },
195 184 icon: "share",
196 185 isEnabled: function(dashboard) {
197   - return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid);
  186 + return dashboard && !dashboard.publicCustomerId;
198 187 }
199 188 });
200   -
201 189 dashboardActionsList.push({
  190 + onAction: function ($event, item) {
  191 + makePrivate($event, item);
  192 + },
  193 + name: function() { return $translate.instant('action.make-private') },
  194 + details: function() { return $translate.instant('dashboard.make-private') },
  195 + icon: "reply",
  196 + isEnabled: function(dashboard) {
  197 + return dashboard && dashboard.publicCustomerId;
  198 + }
  199 + });
  200 + dashboardActionsList.push({
  201 + onAction: function ($event, item) {
  202 + manageAssignedCustomers($event, item);
  203 + },
  204 + name: function() { return $translate.instant('action.assign') },
  205 + details: function() { return $translate.instant('dashboard.manage-assigned-customers') },
  206 + icon: "assignment_ind",
  207 + isEnabled: function(dashboard) {
  208 + return dashboard;
  209 + }
  210 + });
  211 +
  212 + /*dashboardActionsList.push({
202 213 onAction: function ($event, item) {
203 214 assignToCustomer($event, [ item.id.id ]);
204 215 },
... ... @@ -208,8 +219,8 @@ export function DashboardsController(userService, dashboardService, customerServ
208 219 isEnabled: function(dashboard) {
209 220 return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid);
210 221 }
211   - });
212   - dashboardActionsList.push({
  222 + });*/
  223 + /*dashboardActionsList.push({
213 224 onAction: function ($event, item) {
214 225 unassignFromCustomer($event, item, false);
215 226 },
... ... @@ -219,18 +230,7 @@ export function DashboardsController(userService, dashboardService, customerServ
219 230 isEnabled: function(dashboard) {
220 231 return dashboard && dashboard.customerId && dashboard.customerId.id !== types.id.nullUid && !dashboard.assignedCustomer.isPublic;
221 232 }
222   - });
223   - dashboardActionsList.push({
224   - onAction: function ($event, item) {
225   - unassignFromCustomer($event, item, true);
226   - },
227   - name: function() { return $translate.instant('action.make-private') },
228   - details: function() { return $translate.instant('dashboard.make-private') },
229   - icon: "reply",
230   - isEnabled: function(dashboard) {
231   - return dashboard && dashboard.customerId && dashboard.customerId.id !== types.id.nullUid && dashboard.assignedCustomer.isPublic;
232   - }
233   - });
  233 + });*/
234 234
235 235 dashboardActionsList.push(
236 236 {
... ... @@ -246,7 +246,7 @@ export function DashboardsController(userService, dashboardService, customerServ
246 246 dashboardGroupActionsList.push(
247 247 {
248 248 onAction: function ($event, items) {
249   - assignDashboardsToCustomer($event, items);
  249 + assignDashboardsToCustomers($event, items);
250 250 },
251 251 name: function() { return $translate.instant('dashboard.assign-dashboards') },
252 252 details: function(selectedCount) {
... ... @@ -255,6 +255,17 @@ export function DashboardsController(userService, dashboardService, customerServ
255 255 icon: "assignment_ind"
256 256 }
257 257 );
  258 + dashboardGroupActionsList.push(
  259 + {
  260 + onAction: function ($event, items) {
  261 + unassignDashboardsFromCustomers($event, items);
  262 + },
  263 + name: function() { return $translate.instant('dashboard.unassign-dashboards') },
  264 + details: function(selectedCount) {
  265 + return $translate.instant('dashboard.unassign-dashboards-action-text', {count: selectedCount}, "messageformat");
  266 + },
  267 + icon: "assignment_return" }
  268 + );
258 269
259 270 dashboardGroupActionsList.push(
260 271 {
... ... @@ -290,10 +301,10 @@ export function DashboardsController(userService, dashboardService, customerServ
290 301 });
291 302 } else if (vm.dashboardsScope === 'customer' || vm.dashboardsScope === 'customer_user') {
292 303 fetchDashboardsFunction = function (pageLink) {
293   - return dashboardService.getCustomerDashboards(customerId, pageLink, true);
  304 + return dashboardService.getCustomerDashboards(customerId, pageLink);
294 305 };
295 306 deleteDashboardFunction = function (dashboardId) {
296   - return dashboardService.unassignDashboardFromCustomer(dashboardId);
  307 + return dashboardService.unassignDashboardFromCustomer(customerId, dashboardId);
297 308 };
298 309 refreshDashboardsParamsFunction = function () {
299 310 return {"customerId": customerId, "topIndex": vm.topIndex};
... ... @@ -314,26 +325,27 @@ export function DashboardsController(userService, dashboardService, customerServ
314 325 dashboardActionsList.push(
315 326 {
316 327 onAction: function ($event, item) {
317   - unassignFromCustomer($event, item, false);
  328 + makePrivate($event, item);
318 329 },
319   - name: function() { return $translate.instant('action.unassign') },
320   - details: function() { return $translate.instant('dashboard.unassign-from-customer') },
321   - icon: "assignment_return",
  330 + name: function() { return $translate.instant('action.make-private') },
  331 + details: function() { return $translate.instant('dashboard.make-private') },
  332 + icon: "reply",
322 333 isEnabled: function(dashboard) {
323   - return dashboard && !dashboard.assignedCustomer.isPublic;
  334 + return dashboard && customerId == dashboard.publicCustomerId;
324 335 }
325 336 }
326 337 );
  338 +
327 339 dashboardActionsList.push(
328 340 {
329 341 onAction: function ($event, item) {
330   - unassignFromCustomer($event, item, true);
  342 + unassignFromCustomer($event, item, customerId);
331 343 },
332   - name: function() { return $translate.instant('action.make-private') },
333   - details: function() { return $translate.instant('dashboard.make-private') },
334   - icon: "reply",
  344 + name: function() { return $translate.instant('action.unassign') },
  345 + details: function() { return $translate.instant('dashboard.unassign-from-customer') },
  346 + icon: "assignment_return",
335 347 isEnabled: function(dashboard) {
336   - return dashboard && dashboard.assignedCustomer.isPublic;
  348 + return dashboard && customerId != dashboard.publicCustomerId;
337 349 }
338 350 }
339 351 );
... ... @@ -341,7 +353,7 @@ export function DashboardsController(userService, dashboardService, customerServ
341 353 dashboardGroupActionsList.push(
342 354 {
343 355 onAction: function ($event, items) {
344   - unassignDashboardsFromCustomer($event, items);
  356 + unassignDashboardsFromCustomer($event, items, customerId);
345 357 },
346 358 name: function() { return $translate.instant('dashboard.unassign-dashboards') },
347 359 details: function(selectedCount) {
... ... @@ -351,7 +363,6 @@ export function DashboardsController(userService, dashboardService, customerServ
351 363 }
352 364 );
353 365
354   -
355 366 vm.dashboardGridConfig.addItemAction = {
356 367 onAction: function ($event) {
357 368 addDashboardsToCustomer($event);
... ... @@ -428,39 +439,42 @@ export function DashboardsController(userService, dashboardService, customerServ
428 439 return deferred.promise;
429 440 }
430 441
431   - function assignToCustomer($event, dashboardIds) {
  442 + function manageAssignedCustomers($event, dashboard) {
  443 + showManageAssignedCustomersDialog($event, [dashboard.id.id], 'manage', dashboard.assignedCustomersIds);
  444 + }
  445 +
  446 + function assignDashboardsToCustomers($event, items) {
  447 + var dashboardIds = [];
  448 + for (var id in items.selections) {
  449 + dashboardIds.push(id);
  450 + }
  451 + showManageAssignedCustomersDialog($event, dashboardIds, 'assign');
  452 + }
  453 +
  454 + function unassignDashboardsFromCustomers($event, items) {
  455 + var dashboardIds = [];
  456 + for (var id in items.selections) {
  457 + dashboardIds.push(id);
  458 + }
  459 + showManageAssignedCustomersDialog($event, dashboardIds, 'unassign');
  460 + }
  461 +
  462 + function showManageAssignedCustomersDialog($event, dashboardIds, actionType, assignedCustomers) {
432 463 if ($event) {
433 464 $event.stopPropagation();
434 465 }
435   - var pageSize = 10;
436   - customerService.getCustomers({limit: pageSize, textSearch: ''}).then(
437   - function success(_customers) {
438   - var customers = {
439   - pageSize: pageSize,
440   - data: _customers.data,
441   - nextPageLink: _customers.nextPageLink,
442   - selection: null,
443   - hasNext: _customers.hasNext,
444   - pending: false
445   - };
446   - if (customers.hasNext) {
447   - customers.nextPageLink.limit = pageSize;
448   - }
449   - $mdDialog.show({
450   - controller: 'AssignDashboardToCustomerController',
451   - controllerAs: 'vm',
452   - templateUrl: assignToCustomerTemplate,
453   - locals: {dashboardIds: dashboardIds, customers: customers},
454   - parent: angular.element($document[0].body),
455   - fullscreen: true,
456   - targetEvent: $event
457   - }).then(function () {
458   - vm.grid.refreshList();
459   - }, function () {
460   - });
461   - },
462   - function fail() {
463   - });
  466 + $mdDialog.show({
  467 + controller: 'ManageAssignedCustomersController',
  468 + controllerAs: 'vm',
  469 + templateUrl: manageAssignedCustomersTemplate,
  470 + locals: {actionType: actionType, dashboardIds: dashboardIds, assignedCustomers: assignedCustomers},
  471 + parent: angular.element($document[0].body),
  472 + fullscreen: true,
  473 + targetEvent: $event
  474 + }).then(function () {
  475 + vm.grid.refreshList();
  476 + }, function () {
  477 + });
464 478 }
465 479
466 480 function addDashboardsToCustomer($event) {
... ... @@ -468,7 +482,7 @@ export function DashboardsController(userService, dashboardService, customerServ
468 482 $event.stopPropagation();
469 483 }
470 484 var pageSize = 10;
471   - dashboardService.getTenantDashboards({limit: pageSize, textSearch: ''}, false).then(
  485 + dashboardService.getTenantDashboards({limit: pageSize, textSearch: ''}).then(
472 486 function success(_dashboards) {
473 487 var dashboards = {
474 488 pageSize: pageSize,
... ... @@ -499,30 +513,13 @@ export function DashboardsController(userService, dashboardService, customerServ
499 513 });
500 514 }
501 515
502   - function assignDashboardsToCustomer($event, items) {
503   - var dashboardIds = [];
504   - for (var id in items.selections) {
505   - dashboardIds.push(id);
506   - }
507   - assignToCustomer($event, dashboardIds);
508   - }
509   -
510   - function unassignFromCustomer($event, dashboard, isPublic) {
  516 + function unassignFromCustomer($event, dashboard, customerId) {
511 517 if ($event) {
512 518 $event.stopPropagation();
513 519 }
514   - var title;
515   - var content;
516   - var label;
517   - if (isPublic) {
518   - title = $translate.instant('dashboard.make-private-dashboard-title', {dashboardTitle: dashboard.title});
519   - content = $translate.instant('dashboard.make-private-dashboard-text');
520   - label = $translate.instant('dashboard.make-private-dashboard');
521   - } else {
522   - title = $translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title});
523   - content = $translate.instant('dashboard.unassign-dashboard-text');
524   - label = $translate.instant('dashboard.unassign-dashboard');
525   - }
  520 + var title = $translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title});
  521 + var content = $translate.instant('dashboard.unassign-dashboard-text');
  522 + var label = $translate.instant('dashboard.unassign-dashboard');
526 523 var confirm = $mdDialog.confirm()
527 524 .targetEvent($event)
528 525 .title(title)
... ... @@ -531,7 +528,7 @@ export function DashboardsController(userService, dashboardService, customerServ
531 528 .cancel($translate.instant('action.no'))
532 529 .ok($translate.instant('action.yes'));
533 530 $mdDialog.show(confirm).then(function () {
534   - dashboardService.unassignDashboardFromCustomer(dashboard.id.id).then(function success() {
  531 + dashboardService.unassignDashboardFromCustomer(customerId, dashboard.id.id).then(function success() {
535 532 vm.grid.refreshList();
536 533 });
537 534 });
... ... @@ -556,12 +553,33 @@ export function DashboardsController(userService, dashboardService, customerServ
556 553 });
557 554 }
558 555
  556 + function makePrivate($event, dashboard) {
  557 + if ($event) {
  558 + $event.stopPropagation();
  559 + }
  560 + var title = $translate.instant('dashboard.make-private-dashboard-title', {dashboardTitle: dashboard.title});
  561 + var content = $translate.instant('dashboard.make-private-dashboard-text');
  562 + var label = $translate.instant('dashboard.make-private-dashboard');
  563 + var confirm = $mdDialog.confirm()
  564 + .targetEvent($event)
  565 + .title(title)
  566 + .htmlContent(content)
  567 + .ariaLabel(label)
  568 + .cancel($translate.instant('action.no'))
  569 + .ok($translate.instant('action.yes'));
  570 + $mdDialog.show(confirm).then(function () {
  571 + dashboardService.makeDashboardPrivate(dashboard.id.id).then(function success() {
  572 + vm.grid.refreshList();
  573 + });
  574 + });
  575 + }
  576 +
559 577 function exportDashboard($event, dashboard) {
560 578 $event.stopPropagation();
561 579 importExport.exportDashboard(dashboard.id.id);
562 580 }
563 581
564   - function unassignDashboardsFromCustomer($event, items) {
  582 + function unassignDashboardsFromCustomer($event, items, customerId) {
565 583 var confirm = $mdDialog.confirm()
566 584 .targetEvent($event)
567 585 .title($translate.instant('dashboard.unassign-dashboards-title', {count: items.selectedCount}, 'messageformat'))
... ... @@ -572,7 +590,7 @@ export function DashboardsController(userService, dashboardService, customerServ
572 590 $mdDialog.show(confirm).then(function () {
573 591 var tasks = [];
574 592 for (var id in items.selections) {
575   - tasks.push(dashboardService.unassignDashboardFromCustomer(id));
  593 + tasks.push(dashboardService.unassignDashboardFromCustomer(customerId, id));
576 594 }
577 595 $q.all(tasks).then(function () {
578 596 vm.grid.refreshList();
... ...
... ... @@ -25,10 +25,12 @@
25 25 <tb-dashboard-details dashboard="vm.grid.operatingItem()"
26 26 is-edit="vm.grid.detailsConfig.isDetailsEditMode"
27 27 dashboard-scope="vm.dashboardsScope"
  28 + customer-id="vm.customerId"
28 29 the-form="vm.grid.detailsForm"
29   - on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
30 30 on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)"
31   - on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)"
  31 + on-make-private="vm.makePrivate(event, vm.grid.detailsConfig.currentItem)"
  32 + on-manage-assigned-customers="vm.manageAssignedCustomers(event, vm.grid.detailsConfig.currentItem)"
  33 + on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, vm.customerId)"
32 34 on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)"
33 35 on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details>
34 36 </md-tab>
... ...
... ... @@ -40,8 +40,8 @@ import DashboardRoutes from './dashboard.routes';
40 40 import {DashboardsController, DashboardCardController, MakeDashboardPublicDialogController} from './dashboards.controller';
41 41 import DashboardController from './dashboard.controller';
42 42 import DashboardSettingsController from './dashboard-settings.controller';
43   -import AssignDashboardToCustomerController from './assign-to-customer.controller';
44 43 import AddDashboardsToCustomerController from './add-dashboards-to-customer.controller';
  44 +import ManageAssignedCustomersController from './manage-assigned-customers.controller';
45 45 import AddWidgetController from './add-widget.controller';
46 46 import DashboardDirective from './dashboard.directive';
47 47 import EditWidgetDirective from './edit-widget.directive';
... ... @@ -74,8 +74,8 @@ export default angular.module('thingsboard.dashboard', [
74 74 .controller('MakeDashboardPublicDialogController', MakeDashboardPublicDialogController)
75 75 .controller('DashboardController', DashboardController)
76 76 .controller('DashboardSettingsController', DashboardSettingsController)
77   - .controller('AssignDashboardToCustomerController', AssignDashboardToCustomerController)
78 77 .controller('AddDashboardsToCustomerController', AddDashboardsToCustomerController)
  78 + .controller('ManageAssignedCustomersController', ManageAssignedCustomersController)
79 79 .controller('AddWidgetController', AddWidgetController)
80 80 .directive('tbDashboardDetails', DashboardDirective)
81 81 .directive('tbEditWidget', EditWidgetDirective)
... ...
  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 +/*@ngInject*/
  17 +export default function ManageAssignedCustomersController($mdDialog, $q, types, dashboardService, actionType, dashboardIds, assignedCustomers) {
  18 +
  19 + var vm = this;
  20 +
  21 + vm.types = types;
  22 + vm.actionType = actionType;
  23 + vm.dashboardIds = dashboardIds;
  24 + vm.assignedCustomers = assignedCustomers;
  25 + if (actionType != 'manage') {
  26 + vm.assignedCustomers = [];
  27 + }
  28 +
  29 + if (actionType == 'manage') {
  30 + vm.titleText = 'dashboard.manage-assigned-customers';
  31 + vm.labelText = 'dashboard.assigned-customers';
  32 + vm.actionName = 'action.update';
  33 + } else if (actionType == 'assign') {
  34 + vm.titleText = 'dashboard.assign-to-customers';
  35 + vm.labelText = 'dashboard.assign-to-customers-text';
  36 + vm.actionName = 'action.assign';
  37 + } else if (actionType == 'unassign') {
  38 + vm.titleText = 'dashboard.unassign-from-customers';
  39 + vm.labelText = 'dashboard.unassign-from-customers-text';
  40 + vm.actionName = 'action.unassign';
  41 + }
  42 +
  43 + vm.submit = submit;
  44 + vm.cancel = cancel;
  45 +
  46 + function cancel () {
  47 + $mdDialog.cancel();
  48 + }
  49 +
  50 + function submit () {
  51 + var tasks = [];
  52 + for (var i=0;i<vm.dashboardIds.length;i++) {
  53 + var dashboardId = vm.dashboardIds[i];
  54 + var promise;
  55 + if (vm.actionType == 'manage') {
  56 + promise = dashboardService.updateDashboardCustomers(dashboardId, vm.assignedCustomers);
  57 + } else if (vm.actionType == 'assign') {
  58 + promise = dashboardService.addDashboardCustomers(dashboardId, vm.assignedCustomers);
  59 + } else if (vm.actionType == 'unassign') {
  60 + promise = dashboardService.removeDashboardCustomers(dashboardId, vm.assignedCustomers);
  61 + }
  62 + tasks.push(promise);
  63 + }
  64 + $q.all(tasks).then(function () {
  65 + $mdDialog.hide();
  66 + });
  67 + }
  68 +
  69 +}
... ...
ui/src/app/dashboard/manage-assigned-customers.tpl.html renamed from ui/src/app/dashboard/assign-to-customer.tpl.html
... ... @@ -15,11 +15,11 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<md-dialog aria-label="{{ 'dashboard.assign-dashboard-to-customer' | translate }}">
19   - <form name="theForm" ng-submit="vm.assign()">
  18 +<md-dialog aria-label="{{ vm.titleText | translate }}" style="width: 600px;">
  19 + <form name="theForm" ng-submit="vm.submit()">
20 20 <md-toolbar>
21 21 <div class="md-toolbar-tools">
22   - <h2 translate>dashboard.assign-dashboard-to-customer</h2>
  22 + <h2 translate>{{vm.titleText}}</h2>
23 23 <span flex></span>
24 24 <md-button class="md-icon-button" ng-click="vm.cancel()">
25 25 <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
... ... @@ -31,42 +31,17 @@
31 31 <md-dialog-content>
32 32 <div class="md-dialog-content">
33 33 <fieldset>
34   - <span translate>dashboard.assign-to-customer-text</span>
35   - <md-input-container class="md-block" style='margin-bottom: 0px;'>
36   - <label>&nbsp;</label>
37   - <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
38   - search
39   - </md-icon>
40   - <input id="customer-search" autofocus ng-model="vm.searchText"
41   - ng-change="vm.searchCustomerTextUpdated()"
42   - placeholder="{{ 'common.enter-search' | translate }}"/>
43   - </md-input-container>
44   - <div style='min-height: 150px;'>
45   - <span translate layout-align="center center"
46   - style="text-transform: uppercase; display: flex; height: 150px;"
47   - class="md-subhead"
48   - ng-show="vm.noData()">customer.no-customers-text</span>
49   - <md-virtual-repeat-container ng-show="vm.hasData()"
50   - tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
51   - style='min-height: 150px; width: 100%;'>
52   - <md-list>
53   - <md-list-item md-virtual-repeat="customer in vm.theCustomers" md-on-demand
54   - class="repeated-item" flex>
55   - <md-checkbox ng-click="vm.toggleCustomerSelection($event, customer)"
56   - aria-label="{{ 'item.selected' | translate }}"
57   - ng-checked="vm.isCustomerSelected(customer)"></md-checkbox>
58   - <span> {{ customer.title }} </span>
59   - </md-list-item>
60   - </md-list>
61   - </md-virtual-repeat-container>
62   - </div>
  34 + <span translate>{{vm.labelText}}</span>
  35 + <tb-entity-list ng-disabled="$root.loading"
  36 + ng-model="vm.assignedCustomers"
  37 + entity-type="vm.types.entityType.customer"></tb-entity-list>
63 38 </fieldset>
64 39 </div>
65 40 </md-dialog-content>
66 41 <md-dialog-actions layout="row">
67 42 <span flex></span>
68   - <md-button ng-disabled="$root.loading || vm.customers.selection==null" type="submit" class="md-raised md-primary">
69   - {{ 'action.assign' | translate }}
  43 + <md-button ng-disabled="$root.loading || !theForm.$dirty || !theForm.$valid" type="submit" class="md-raised md-primary">
  44 + {{ vm.actionName | translate }}
70 45 </md-button>
71 46 <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
72 47 translate }}
... ...
... ... @@ -38,7 +38,12 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
38 38 if (scope.excludeEntityIds && scope.excludeEntityIds.length) {
39 39 limit += scope.excludeEntityIds.length;
40 40 }
41   - entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit, {ignoreLoading: true}, scope.entitySubtype).then(function success(result) {
  41 + var targetType = scope.entityType;
  42 + if (targetType == types.aliasEntityType.current_customer) {
  43 + targetType = types.entityType.customer;
  44 + }
  45 +
  46 + entityService.getEntitiesByNameFilter(targetType, searchText, limit, {ignoreLoading: true}, scope.entitySubtype).then(function success(result) {
42 47 if (result) {
43 48 if (scope.excludeEntityIds && scope.excludeEntityIds.length) {
44 49 var entities = [];
... ... @@ -71,7 +76,11 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
71 76
72 77 ngModelCtrl.$render = function () {
73 78 if (ngModelCtrl.$viewValue) {
74   - entityService.getEntity(scope.entityType, ngModelCtrl.$viewValue).then(
  79 + var targetType = scope.entityType;
  80 + if (targetType == types.aliasEntityType.current_customer) {
  81 + targetType = types.entityType.customer;
  82 + }
  83 + entityService.getEntity(targetType, ngModelCtrl.$viewValue).then(
75 84 function success(entity) {
76 85 scope.entity = entity;
77 86 },
... ... @@ -114,55 +123,61 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
114 123 scope.selectEntityText = 'asset.select-asset';
115 124 scope.entityText = 'asset.asset';
116 125 scope.noEntitiesMatchingText = 'asset.no-assets-matching';
117   - scope.entityRequiredText = 'asset.asset-required'
  126 + scope.entityRequiredText = 'asset.asset-required';
118 127 break;
119 128 case types.entityType.device:
120 129 scope.selectEntityText = 'device.select-device';
121 130 scope.entityText = 'device.device';
122 131 scope.noEntitiesMatchingText = 'device.no-devices-matching';
123   - scope.entityRequiredText = 'device.device-required'
  132 + scope.entityRequiredText = 'device.device-required';
124 133 break;
125 134 case types.entityType.rule:
126 135 scope.selectEntityText = 'rule.select-rule';
127 136 scope.entityText = 'rule.rule';
128 137 scope.noEntitiesMatchingText = 'rule.no-rules-matching';
129   - scope.entityRequiredText = 'rule.rule-required'
  138 + scope.entityRequiredText = 'rule.rule-required';
130 139 break;
131 140 case types.entityType.plugin:
132 141 scope.selectEntityText = 'plugin.select-plugin';
133 142 scope.entityText = 'plugin.plugin';
134 143 scope.noEntitiesMatchingText = 'plugin.no-plugins-matching';
135   - scope.entityRequiredText = 'plugin.plugin-required'
  144 + scope.entityRequiredText = 'plugin.plugin-required';
136 145 break;
137 146 case types.entityType.tenant:
138 147 scope.selectEntityText = 'tenant.select-tenant';
139 148 scope.entityText = 'tenant.tenant';
140 149 scope.noEntitiesMatchingText = 'tenant.no-tenants-matching';
141   - scope.entityRequiredText = 'tenant.tenant-required'
  150 + scope.entityRequiredText = 'tenant.tenant-required';
142 151 break;
143 152 case types.entityType.customer:
144 153 scope.selectEntityText = 'customer.select-customer';
145 154 scope.entityText = 'customer.customer';
146 155 scope.noEntitiesMatchingText = 'customer.no-customers-matching';
147   - scope.entityRequiredText = 'customer.customer-required'
  156 + scope.entityRequiredText = 'customer.customer-required';
148 157 break;
149 158 case types.entityType.user:
150 159 scope.selectEntityText = 'user.select-user';
151 160 scope.entityText = 'user.user';
152 161 scope.noEntitiesMatchingText = 'user.no-users-matching';
153   - scope.entityRequiredText = 'user.user-required'
  162 + scope.entityRequiredText = 'user.user-required';
154 163 break;
155 164 case types.entityType.dashboard:
156 165 scope.selectEntityText = 'dashboard.select-dashboard';
157 166 scope.entityText = 'dashboard.dashboard';
158 167 scope.noEntitiesMatchingText = 'dashboard.no-dashboards-matching';
159   - scope.entityRequiredText = 'dashboard.dashboard-required'
  168 + scope.entityRequiredText = 'dashboard.dashboard-required';
160 169 break;
161 170 case types.entityType.alarm:
162 171 scope.selectEntityText = 'alarm.select-alarm';
163 172 scope.entityText = 'alarm.alarm';
164 173 scope.noEntitiesMatchingText = 'alarm.no-alarms-matching';
165   - scope.entityRequiredText = 'alarm.alarm-required'
  174 + scope.entityRequiredText = 'alarm.alarm-required';
  175 + break;
  176 + case types.aliasEntityType.current_customer:
  177 + scope.selectEntityText = 'customer.select-default-customer';
  178 + scope.entityText = 'customer.default-customer';
  179 + scope.noEntitiesMatchingText = 'customer.no-customers-matching';
  180 + scope.entityRequiredText = 'customer.default-customer-required';
166 181 break;
167 182 }
168 183 if (scope.entity && scope.entity.id.entityType != scope.entityType) {
... ...
... ... @@ -32,6 +32,7 @@
32 32 <tb-entity-select flex
33 33 the-form="theForm"
34 34 tb-required="true"
  35 + use-alias-entity-types="true"
35 36 ng-model="filter.singleEntity">
36 37 </tb-entity-select>
37 38 </section>
... ... @@ -78,6 +79,7 @@
78 79 <tb-entity-select flex
79 80 the-form="theForm"
80 81 tb-required="false"
  82 + use-alias-entity-types="true"
81 83 ng-model="filter.defaultStateEntity">
82 84 </tb-entity-select>
83 85 </div>
... ... @@ -123,6 +125,7 @@
123 125 the-form="theForm"
124 126 tb-required="!filter.rootStateEntity"
125 127 ng-disabled="filter.rootStateEntity"
  128 + use-alias-entity-types="true"
126 129 ng-model="filter.rootEntity">
127 130 </tb-entity-select>
128 131 </div>
... ... @@ -139,6 +142,7 @@
139 142 <tb-entity-select flex
140 143 the-form="theForm"
141 144 tb-required="false"
  145 + use-alias-entity-types="true"
142 146 ng-model="filter.defaultStateEntity">
143 147 </tb-entity-select>
144 148 </div>
... ... @@ -182,6 +186,7 @@
182 186 the-form="theForm"
183 187 tb-required="!filter.rootStateEntity"
184 188 ng-disabled="filter.rootStateEntity"
  189 + use-alias-entity-types="true"
185 190 ng-model="filter.rootEntity">
186 191 </tb-entity-select>
187 192 </div>
... ... @@ -198,6 +203,7 @@
198 203 <tb-entity-select flex
199 204 the-form="theForm"
200 205 tb-required="false"
  206 + use-alias-entity-types="true"
201 207 ng-model="filter.defaultStateEntity">
202 208 </tb-entity-select>
203 209 </div>
... ... @@ -249,6 +255,7 @@
249 255 the-form="theForm"
250 256 tb-required="!filter.rootStateEntity"
251 257 ng-disabled="filter.rootStateEntity"
  258 + use-alias-entity-types="true"
252 259 ng-model="filter.rootEntity">
253 260 </tb-entity-select>
254 261 </div>
... ... @@ -265,6 +272,7 @@
265 272 <tb-entity-select flex
266 273 the-form="theForm"
267 274 tb-required="false"
  275 + use-alias-entity-types="true"
268 276 ng-model="filter.defaultStateEntity">
269 277 </tb-entity-select>
270 278 </div>
... ...
... ... @@ -105,7 +105,8 @@ export default function EntitySelect($compile, $templateCache) {
105 105 scope: {
106 106 theForm: '=?',
107 107 tbRequired: '=?',
108   - disabled:'=ngDisabled'
  108 + disabled:'=ngDisabled',
  109 + useAliasEntityTypes: "=?"
109 110 }
110 111 };
111 112 }
... ...
... ... @@ -20,6 +20,7 @@
20 20 the-form="theForm"
21 21 ng-disabled="disabled"
22 22 tb-required="tbRequired"
  23 + use-alias-entity-types="useAliasEntityTypes"
23 24 ng-model="model.entityType">
24 25 </tb-entity-type-select>
25 26 <tb-entity-autocomplete flex ng-if="model.entityType"
... ...
... ... @@ -39,7 +39,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity
39 39
40 40 scope.ngModelCtrl = ngModelCtrl;
41 41
42   - scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
  42 + scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes, scope.useAliasEntityTypes);
43 43
44 44 scope.typeName = function(type) {
45 45 return type ? types.entityTypeTranslations[type].type : '';
... ... @@ -79,7 +79,8 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity
79 79 theForm: '=?',
80 80 tbRequired: '=?',
81 81 disabled:'=ngDisabled',
82   - allowedEntityTypes: "=?"
  82 + allowedEntityTypes: "=?",
  83 + useAliasEntityTypes: "=?"
83 84 }
84 85 };
85 86 }
... ...
... ... @@ -540,7 +540,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
540 540 function success(dashboard) {
541 541 var name = dashboard.title;
542 542 name = name.toLowerCase().replace(/\W/g,"_");
543   - exportToPc(prepareExport(dashboard), name + '.json');
  543 + exportToPc(prepareDashboardExport(dashboard), name + '.json');
544 544 },
545 545 function fail(rejection) {
546 546 var message = rejection;
... ... @@ -552,6 +552,15 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
552 552 );
553 553 }
554 554
  555 + function prepareDashboardExport(dashboard) {
  556 + dashboard = prepareExport(dashboard);
  557 + delete dashboard.assignedCustomers;
  558 + delete dashboard.publicCustomerId;
  559 + delete dashboard.assignedCustomersText;
  560 + delete dashboard.assignedCustomersIds;
  561 + return dashboard;
  562 + }
  563 +
555 564 function importDashboard($event) {
556 565 var deferred = $q.defer();
557 566 openImportDialog($event, 'dashboard.import', 'dashboard.dashboard-file').then(
... ...
... ... @@ -383,7 +383,10 @@ export default angular.module('thingsboard.locale', [])
383 383 "idCopiedMessage": "Customer Id has been copied to clipboard",
384 384 "select-customer": "Select customer",
385 385 "no-customers-matching": "No customers matching '{{entity}}' were found.",
386   - "customer-required": "Customer is required"
  386 + "customer-required": "Customer is required",
  387 + "select-default-customer": "Select default customer",
  388 + "default-customer": "Default customer",
  389 + "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level"
387 390 },
388 391 "datetime": {
389 392 "date-from": "Date from",
... ... @@ -404,6 +407,12 @@ export default angular.module('thingsboard.locale', [])
404 407 "unassign-from-customer": "Unassign from customer",
405 408 "make-public": "Make dashboard public",
406 409 "make-private": "Make dashboard private",
  410 + "manage-assigned-customers": "Manage assigned customers",
  411 + "assigned-customers": "Assigned customers",
  412 + "assign-to-customers": "Assign Dashboard(s) To Customers",
  413 + "assign-to-customers-text": "Please select the customers to assign the dashboard(s)",
  414 + "unassign-from-customers": "Unassign Dashboard(s) From Customers",
  415 + "unassign-from-customers-text": "Please select the customers to unassign from the dashboard(s)",
407 416 "no-dashboards-text": "No dashboards found",
408 417 "no-widgets": "No widgets configured",
409 418 "add-widget": "Add new widget",
... ... @@ -418,7 +427,8 @@ export default angular.module('thingsboard.locale', [])
418 427 "add-dashboard-text": "Add new dashboard",
419 428 "assign-dashboards": "Assign dashboards",
420 429 "assign-new-dashboard": "Assign new dashboard",
421   - "assign-dashboards-text": "Assign { count, select, 1 {1 dashboard} other {# dashboards} } to customer",
  430 + "assign-dashboards-text": "Assign { count, select, 1 {1 dashboard} other {# dashboards} } to customers",
  431 + "unassign-dashboards-action-text": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customers",
422 432 "delete-dashboards": "Delete dashboards",
423 433 "unassign-dashboards": "Unassign dashboards",
424 434 "unassign-dashboards-action-title": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customer",
... ... @@ -500,6 +510,7 @@ export default angular.module('thingsboard.locale', [])
500 510 "Please contact your administrator in order to resolve this issue.",
501 511 "select-devices": "Select devices",
502 512 "assignedToCustomer": "Assigned to customer",
  513 + "assignedToCustomers": "Assigned to customers",
503 514 "public": "Public",
504 515 "public-link": "Public link",
505 516 "copy-public-link": "Copy public link",
... ... @@ -735,6 +746,7 @@ export default angular.module('thingsboard.locale', [])
735 746 "type-alarms": "Alarms",
736 747 "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
737 748 "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
  749 + "type-current-customer": "Current Customer",
738 750 "search": "Search entities",
739 751 "selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
740 752 "entity-name": "Entity name",
... ...