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,3 +87,26 @@ CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions (
87 PRIMARY KEY (( tenant_id ), partition) 87 PRIMARY KEY (( tenant_id ), partition)
88 ) WITH CLUSTERING ORDER BY ( partition ASC ) 88 ) WITH CLUSTERING ORDER BY ( partition ASC )
89 AND compaction = { 'class' : 'LeveledCompactionStrategy' }; 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,3 +29,13 @@ CREATE TABLE IF NOT EXISTS audit_log (
29 action_failure_details varchar(1000000) 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,7 +423,7 @@ public abstract class BaseController {
423 try { 423 try {
424 validateId(dashboardId, "Incorrect dashboardId " + dashboardId); 424 validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
425 Dashboard dashboard = dashboardService.findDashboardById(dashboardId); 425 Dashboard dashboard = dashboardService.findDashboardById(dashboardId);
426 - checkDashboard(dashboard, true); 426 + checkDashboard(dashboard);
427 return dashboard; 427 return dashboard;
428 } catch (Exception e) { 428 } catch (Exception e) {
429 throw handleException(e, false); 429 throw handleException(e, false);
@@ -434,28 +434,23 @@ public abstract class BaseController { @@ -434,28 +434,23 @@ public abstract class BaseController {
434 try { 434 try {
435 validateId(dashboardId, "Incorrect dashboardId " + dashboardId); 435 validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
436 DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId); 436 DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId);
437 - SecurityUser authUser = getCurrentUser();  
438 - checkDashboard(dashboardInfo, authUser.getAuthority() != Authority.SYS_ADMIN); 437 + checkDashboard(dashboardInfo);
439 return dashboardInfo; 438 return dashboardInfo;
440 } catch (Exception e) { 439 } catch (Exception e) {
441 throw handleException(e, false); 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 checkNotNull(dashboard); 445 checkNotNull(dashboard);
447 checkTenantId(dashboard.getTenantId()); 446 checkTenantId(dashboard.getTenantId());
448 SecurityUser authUser = getCurrentUser(); 447 SecurityUser authUser = getCurrentUser();
449 if (authUser.getAuthority() == Authority.CUSTOMER_USER) { 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 throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, 450 throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
452 ThingsboardErrorCode.PERMISSION_DENIED); 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 ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException { 456 ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException {
@@ -18,20 +18,22 @@ package org.thingsboard.server.controller; @@ -18,20 +18,22 @@ package org.thingsboard.server.controller;
18 import org.springframework.http.HttpStatus; 18 import org.springframework.http.HttpStatus;
19 import org.springframework.security.access.prepost.PreAuthorize; 19 import org.springframework.security.access.prepost.PreAuthorize;
20 import org.springframework.web.bind.annotation.*; 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 import org.thingsboard.server.common.data.audit.ActionType; 22 import org.thingsboard.server.common.data.audit.ActionType;
26 import org.thingsboard.server.common.data.id.CustomerId; 23 import org.thingsboard.server.common.data.id.CustomerId;
27 import org.thingsboard.server.common.data.id.DashboardId; 24 import org.thingsboard.server.common.data.id.DashboardId;
28 import org.thingsboard.server.common.data.id.TenantId; 25 import org.thingsboard.server.common.data.id.TenantId;
29 import org.thingsboard.server.common.data.page.TextPageData; 26 import org.thingsboard.server.common.data.page.TextPageData;
30 import org.thingsboard.server.common.data.page.TextPageLink; 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 import org.thingsboard.server.dao.exception.IncorrectParameterException; 30 import org.thingsboard.server.dao.exception.IncorrectParameterException;
32 import org.thingsboard.server.dao.model.ModelConstants; 31 import org.thingsboard.server.dao.model.ModelConstants;
33 import org.thingsboard.server.exception.ThingsboardException; 32 import org.thingsboard.server.exception.ThingsboardException;
34 33
  34 +import java.util.HashSet;
  35 +import java.util.Set;
  36 +
35 @RestController 37 @RestController
36 @RequestMapping("/api") 38 @RequestMapping("/api")
37 public class DashboardController extends BaseController { 39 public class DashboardController extends BaseController {
@@ -80,7 +82,7 @@ public class DashboardController extends BaseController { @@ -80,7 +82,7 @@ public class DashboardController extends BaseController {
80 Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard)); 82 Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard));
81 83
82 logEntityAction(savedDashboard.getId(), savedDashboard, 84 logEntityAction(savedDashboard.getId(), savedDashboard,
83 - savedDashboard.getCustomerId(), 85 + null,
84 dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); 86 dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
85 87
86 return savedDashboard; 88 return savedDashboard;
@@ -103,7 +105,7 @@ public class DashboardController extends BaseController { @@ -103,7 +105,7 @@ public class DashboardController extends BaseController {
103 dashboardService.deleteDashboard(dashboardId); 105 dashboardService.deleteDashboard(dashboardId);
104 106
105 logEntityAction(dashboardId, dashboard, 107 logEntityAction(dashboardId, dashboard,
106 - dashboard.getCustomerId(), 108 + null,
107 ActionType.DELETED, null, strDashboardId); 109 ActionType.DELETED, null, strDashboardId);
108 110
109 } catch (Exception e) { 111 } catch (Exception e) {
@@ -134,7 +136,7 @@ public class DashboardController extends BaseController { @@ -134,7 +136,7 @@ public class DashboardController extends BaseController {
134 Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId)); 136 Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
135 137
136 logEntityAction(dashboardId, savedDashboard, 138 logEntityAction(dashboardId, savedDashboard,
137 - savedDashboard.getCustomerId(), 139 + customerId,
138 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName()); 140 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName());
139 141
140 142
@@ -150,23 +152,22 @@ public class DashboardController extends BaseController { @@ -150,23 +152,22 @@ public class DashboardController extends BaseController {
150 } 152 }
151 153
152 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 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 @ResponseBody 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 checkParameter(DASHBOARD_ID, strDashboardId); 160 checkParameter(DASHBOARD_ID, strDashboardId);
157 try { 161 try {
  162 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  163 + Customer customer = checkCustomerId(customerId);
158 DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); 164 DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
159 Dashboard dashboard = checkDashboardId(dashboardId); 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 logEntityAction(dashboardId, dashboard, 169 logEntityAction(dashboardId, dashboard,
169 - dashboard.getCustomerId(), 170 + customerId,
170 ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName()); 171 ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName());
171 172
172 return savedDashboard; 173 return savedDashboard;
@@ -181,6 +182,158 @@ public class DashboardController extends BaseController { @@ -181,6 +182,158 @@ public class DashboardController extends BaseController {
181 } 182 }
182 183
183 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 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 @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST) 337 @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST)
185 @ResponseBody 338 @ResponseBody
186 public Dashboard assignDashboardToPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { 339 public Dashboard assignDashboardToPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
@@ -192,7 +345,7 @@ public class DashboardController extends BaseController { @@ -192,7 +345,7 @@ public class DashboardController extends BaseController {
192 Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId())); 345 Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId()));
193 346
194 logEntityAction(dashboardId, savedDashboard, 347 logEntityAction(dashboardId, savedDashboard,
195 - savedDashboard.getCustomerId(), 348 + publicCustomer.getId(),
196 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName()); 349 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
197 350
198 return savedDashboard; 351 return savedDashboard;
@@ -206,6 +359,33 @@ public class DashboardController extends BaseController { @@ -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 @PreAuthorize("hasAuthority('SYS_ADMIN')") 389 @PreAuthorize("hasAuthority('SYS_ADMIN')")
210 @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET) 390 @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
211 @ResponseBody 391 @ResponseBody
@@ -245,19 +425,20 @@ public class DashboardController extends BaseController { @@ -245,19 +425,20 @@ public class DashboardController extends BaseController {
245 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 425 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
246 @RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET) 426 @RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
247 @ResponseBody 427 @ResponseBody
248 - public TextPageData<DashboardInfo> getCustomerDashboards( 428 + public TimePageData<DashboardInfo> getCustomerDashboards(
249 @PathVariable("customerId") String strCustomerId, 429 @PathVariable("customerId") String strCustomerId,
250 @RequestParam int limit, 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 checkParameter("customerId", strCustomerId); 435 checkParameter("customerId", strCustomerId);
255 try { 436 try {
256 TenantId tenantId = getCurrentUser().getTenantId(); 437 TenantId tenantId = getCurrentUser().getTenantId();
257 CustomerId customerId = new CustomerId(toUUID(strCustomerId)); 438 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
258 checkCustomerId(customerId); 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 } catch (Exception e) { 442 } catch (Exception e) {
262 throw handleException(e); 443 throw handleException(e);
263 } 444 }
@@ -24,6 +24,7 @@ import org.springframework.context.annotation.Profile; @@ -24,6 +24,7 @@ import org.springframework.context.annotation.Profile;
24 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
25 import org.thingsboard.server.dao.cassandra.CassandraCluster; 25 import org.thingsboard.server.dao.cassandra.CassandraCluster;
26 import org.thingsboard.server.dao.cassandra.CassandraInstallCluster; 26 import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
  27 +import org.thingsboard.server.dao.dashboard.DashboardService;
27 import org.thingsboard.server.dao.util.NoSqlDao; 28 import org.thingsboard.server.dao.util.NoSqlDao;
28 import org.thingsboard.server.service.install.cql.CQLStatementsParser; 29 import org.thingsboard.server.service.install.cql.CQLStatementsParser;
29 import org.thingsboard.server.service.install.cql.CassandraDbHelper; 30 import org.thingsboard.server.service.install.cql.CassandraDbHelper;
@@ -33,6 +34,8 @@ import java.nio.file.Path; @@ -33,6 +34,8 @@ import java.nio.file.Path;
33 import java.nio.file.Paths; 34 import java.nio.file.Paths;
34 import java.util.List; 35 import java.util.List;
35 36
  37 +import static org.thingsboard.server.service.install.DatabaseHelper.*;
  38 +
36 @Service 39 @Service
37 @NoSqlDao 40 @NoSqlDao
38 @Profile("install") 41 @Profile("install")
@@ -40,12 +43,6 @@ import java.util.List; @@ -40,12 +43,6 @@ import java.util.List;
40 public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { 43 public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
41 44
42 private static final String SCHEMA_UPDATE_CQL = "schema_update.cql"; 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 @Value("${install.data_dir}") 47 @Value("${install.data_dir}")
51 private String dataDir; 48 private String dataDir;
@@ -56,6 +53,9 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { @@ -56,6 +53,9 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
56 @Autowired 53 @Autowired
57 private CassandraInstallCluster installCluster; 54 private CassandraInstallCluster installCluster;
58 55
  56 + @Autowired
  57 + private DashboardService dashboardService;
  58 +
59 @Override 59 @Override
60 public void upgradeDatabase(String fromVersion) throws Exception { 60 public void upgradeDatabase(String fromVersion) throws Exception {
61 61
@@ -160,10 +160,32 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { @@ -160,10 +160,32 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
160 case "1.3.0": 160 case "1.3.0":
161 break; 161 break;
162 case "1.3.1": 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 log.info("Updating schema ..."); 176 log.info("Updating schema ...");
164 schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL); 177 schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
165 loadCql(schemaUpdateFile); 178 loadCql(schemaUpdateFile);
166 log.info("Schema updated."); 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 break; 189 break;
168 default: 190 default:
169 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); 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,8 +339,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
339 JsonNode dashboardJson = objectMapper.readTree(path.toFile()); 339 JsonNode dashboardJson = objectMapper.readTree(path.toFile());
340 Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class); 340 Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
341 dashboard.setTenantId(tenantId); 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 } catch (Exception e) { 346 } catch (Exception e) {
345 log.error("Unable to load dashboard from json: [{}]", path.toString()); 347 log.error("Unable to load dashboard from json: [{}]", path.toString());
346 throw new RuntimeException("Unable to load dashboard from json", e); 348 throw new RuntimeException("Unable to load dashboard from json", e);
@@ -17,18 +17,26 @@ @@ -17,18 +17,26 @@
17 package org.thingsboard.server.service.install; 17 package org.thingsboard.server.service.install;
18 18
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.beans.factory.annotation.Autowired;
20 import org.springframework.beans.factory.annotation.Value; 21 import org.springframework.beans.factory.annotation.Value;
21 import org.springframework.context.annotation.Profile; 22 import org.springframework.context.annotation.Profile;
22 import org.springframework.stereotype.Service; 23 import org.springframework.stereotype.Service;
  24 +import org.thingsboard.server.dao.dashboard.DashboardService;
23 import org.thingsboard.server.dao.util.SqlDao; 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 import java.nio.charset.Charset; 29 import java.nio.charset.Charset;
26 import java.nio.file.Files; 30 import java.nio.file.Files;
27 import java.nio.file.Path; 31 import java.nio.file.Path;
28 import java.nio.file.Paths; 32 import java.nio.file.Paths;
29 import java.sql.Connection; 33 import java.sql.Connection;
  34 +import java.sql.DatabaseMetaData;
30 import java.sql.DriverManager; 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 @Service 40 @Service
33 @Profile("install") 41 @Profile("install")
34 @Slf4j 42 @Slf4j
@@ -49,6 +57,9 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { @@ -49,6 +57,9 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
49 @Value("${spring.datasource.password}") 57 @Value("${spring.datasource.password}")
50 private String dbPassword; 58 private String dbPassword;
51 59
  60 + @Autowired
  61 + private DashboardService dashboardService;
  62 +
52 @Override 63 @Override
53 public void upgradeDatabase(String fromVersion) throws Exception { 64 public void upgradeDatabase(String fromVersion) throws Exception {
54 switch (fromVersion) { 65 switch (fromVersion) {
@@ -62,13 +73,30 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { @@ -62,13 +73,30 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
62 log.info("Schema updated."); 73 log.info("Schema updated.");
63 break; 74 break;
64 case "1.3.1": 75 case "1.3.1":
65 - log.info("Updating schema ...");  
66 - schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);  
67 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { 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 String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8")); 87 String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
69 conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script 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 break; 100 break;
73 default: 101 default:
74 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); 102 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
@@ -28,16 +28,25 @@ import java.nio.file.Path; @@ -28,16 +28,25 @@ import java.nio.file.Path;
28 import java.nio.file.StandardCopyOption; 28 import java.nio.file.StandardCopyOption;
29 import java.util.*; 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 public static Path dumpCfIfExists(KeyspaceMetadata ks, Session session, String cfName, 35 public static Path dumpCfIfExists(KeyspaceMetadata ks, Session session, String cfName,
36 String[] columns, String[] defaultValues, String dumpPrefix) throws Exception { 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 if (ks.getTable(cfName) != null) { 42 if (ks.getTable(cfName) != null) {
38 Path dumpFile = Files.createTempFile(dumpPrefix, null); 43 Path dumpFile = Files.createTempFile(dumpPrefix, null);
39 Files.deleteIfExists(dumpFile); 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 Statement stmt = new SimpleStatement("SELECT * FROM " + cfName); 50 Statement stmt = new SimpleStatement("SELECT * FROM " + cfName);
42 stmt.setFetchSize(1000); 51 stmt.setFetchSize(1000);
43 ResultSet rs = session.execute(stmt); 52 ResultSet rs = session.execute(stmt);
@@ -75,9 +84,19 @@ public class CassandraDbHelper { @@ -75,9 +84,19 @@ public class CassandraDbHelper {
75 } 84 }
76 85
77 public static void loadCf(KeyspaceMetadata ks, Session session, String cfName, String[] columns, Path sourceFile) throws Exception { 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 TableMetadata tableMetadata = ks.getTable(cfName); 91 TableMetadata tableMetadata = ks.getTable(cfName);
79 PreparedStatement prepared = session.prepare(createInsertStatement(cfName, columns)); 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 csvParser.forEach(record -> { 100 csvParser.forEach(record -> {
82 BoundStatement boundStatement = prepared.bind(); 101 BoundStatement boundStatement = prepared.bind();
83 for (String column : columns) { 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,6 +19,7 @@ import static org.hamcrest.Matchers.containsString;
19 import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; 19 import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
20 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 20 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
21 21
  22 +import java.sql.Time;
22 import java.util.ArrayList; 23 import java.util.ArrayList;
23 import java.util.Collections; 24 import java.util.Collections;
24 import java.util.List; 25 import java.util.List;
@@ -29,6 +30,8 @@ import org.thingsboard.server.common.data.*; @@ -29,6 +30,8 @@ import org.thingsboard.server.common.data.*;
29 import org.thingsboard.server.common.data.id.CustomerId; 30 import org.thingsboard.server.common.data.id.CustomerId;
30 import org.thingsboard.server.common.data.page.TextPageData; 31 import org.thingsboard.server.common.data.page.TextPageData;
31 import org.thingsboard.server.common.data.page.TextPageLink; 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 import org.thingsboard.server.common.data.security.Authority; 35 import org.thingsboard.server.common.data.security.Authority;
33 import org.thingsboard.server.dao.model.ModelConstants; 36 import org.thingsboard.server.dao.model.ModelConstants;
34 import org.junit.After; 37 import org.junit.After;
@@ -82,8 +85,6 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -82,8 +85,6 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
82 Assert.assertNotNull(savedDashboard.getId()); 85 Assert.assertNotNull(savedDashboard.getId());
83 Assert.assertTrue(savedDashboard.getCreatedTime() > 0); 86 Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
84 Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId()); 87 Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId());
85 - Assert.assertNotNull(savedDashboard.getCustomerId());  
86 - Assert.assertEquals(NULL_UUID, savedDashboard.getCustomerId().getId());  
87 Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle()); 88 Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
88 89
89 savedDashboard.setTitle("My new dashboard"); 90 savedDashboard.setTitle("My new dashboard");
@@ -136,17 +137,20 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -136,17 +137,20 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
136 137
137 Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString() 138 Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
138 + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); 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 Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); 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 Dashboard unassignedDashboard = 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 foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); 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 @Test 156 @Test
@@ -320,11 +324,11 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -320,11 +324,11 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
320 } 324 }
321 325
322 List<DashboardInfo> loadedDashboards = new ArrayList<>(); 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 do { 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 loadedDashboards.addAll(pageData.getData()); 332 loadedDashboards.addAll(pageData.getData());
329 if (pageData.hasNext()) { 333 if (pageData.hasNext()) {
330 pageLink = pageData.getNextPageLink(); 334 pageLink = pageData.getNextPageLink();
@@ -336,93 +340,5 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -336,93 +340,5 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
336 340
337 Assert.assertEquals(dashboards, loadedDashboards); 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,6 +69,11 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
69 return false; 69 return false;
70 } 70 }
71 71
  72 + @JsonIgnore
  73 + public ShortCustomerInfo toShortCustomerInfo() {
  74 + return new ShortCustomerInfo(id, title, isPublic());
  75 + }
  76 +
72 @Override 77 @Override
73 @JsonProperty(access = Access.READ_ONLY) 78 @JsonProperty(access = Access.READ_ONLY)
74 public String getName() { 79 public String getName() {
@@ -79,8 +79,6 @@ public class Dashboard extends DashboardInfo { @@ -79,8 +79,6 @@ public class Dashboard extends DashboardInfo {
79 StringBuilder builder = new StringBuilder(); 79 StringBuilder builder = new StringBuilder();
80 builder.append("Dashboard [tenantId="); 80 builder.append("Dashboard [tenantId=");
81 builder.append(getTenantId()); 81 builder.append(getTenantId());
82 - builder.append(", customerId=");  
83 - builder.append(getCustomerId());  
84 builder.append(", title="); 82 builder.append(", title=");
85 builder.append(getTitle()); 83 builder.append(getTitle());
86 builder.append(", configuration="); 84 builder.append(", configuration=");
@@ -20,11 +20,13 @@ import org.thingsboard.server.common.data.id.CustomerId; @@ -20,11 +20,13 @@ import org.thingsboard.server.common.data.id.CustomerId;
20 import org.thingsboard.server.common.data.id.DashboardId; 20 import org.thingsboard.server.common.data.id.DashboardId;
21 import org.thingsboard.server.common.data.id.TenantId; 21 import org.thingsboard.server.common.data.id.TenantId;
22 22
  23 +import java.util.*;
  24 +
23 public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName { 25 public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName {
24 26
25 private TenantId tenantId; 27 private TenantId tenantId;
26 - private CustomerId customerId;  
27 private String title; 28 private String title;
  29 + private Set<ShortCustomerInfo> assignedCustomers;
28 30
29 public DashboardInfo() { 31 public DashboardInfo() {
30 super(); 32 super();
@@ -37,8 +39,8 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -37,8 +39,8 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
37 public DashboardInfo(DashboardInfo dashboardInfo) { 39 public DashboardInfo(DashboardInfo dashboardInfo) {
38 super(dashboardInfo); 40 super(dashboardInfo);
39 this.tenantId = dashboardInfo.getTenantId(); 41 this.tenantId = dashboardInfo.getTenantId();
40 - this.customerId = dashboardInfo.getCustomerId();  
41 this.title = dashboardInfo.getTitle(); 42 this.title = dashboardInfo.getTitle();
  43 + this.assignedCustomers = dashboardInfo.getAssignedCustomers();
42 } 44 }
43 45
44 public TenantId getTenantId() { 46 public TenantId getTenantId() {
@@ -49,14 +51,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -49,14 +51,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
49 this.tenantId = tenantId; 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 public String getTitle() { 54 public String getTitle() {
61 return title; 55 return title;
62 } 56 }
@@ -65,6 +59,62 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -65,6 +59,62 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
65 this.title = title; 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 @Override 118 @Override
69 @JsonProperty(access = JsonProperty.Access.READ_ONLY) 119 @JsonProperty(access = JsonProperty.Access.READ_ONLY)
70 public String getName() { 120 public String getName() {
@@ -80,7 +130,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -80,7 +130,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
80 public int hashCode() { 130 public int hashCode() {
81 final int prime = 31; 131 final int prime = 31;
82 int result = super.hashCode(); 132 int result = super.hashCode();
83 - result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());  
84 result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); 133 result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
85 result = prime * result + ((title == null) ? 0 : title.hashCode()); 134 result = prime * result + ((title == null) ? 0 : title.hashCode());
86 return result; 135 return result;
@@ -95,11 +144,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -95,11 +144,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
95 if (getClass() != obj.getClass()) 144 if (getClass() != obj.getClass())
96 return false; 145 return false;
97 DashboardInfo other = (DashboardInfo) obj; 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 if (tenantId == null) { 147 if (tenantId == null) {
104 if (other.tenantId != null) 148 if (other.tenantId != null)
105 return false; 149 return false;
@@ -118,8 +162,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -118,8 +162,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
118 StringBuilder builder = new StringBuilder(); 162 StringBuilder builder = new StringBuilder();
119 builder.append("DashboardInfo [tenantId="); 163 builder.append("DashboardInfo [tenantId=");
120 builder.append(tenantId); 164 builder.append(tenantId);
121 - builder.append(", customerId=");  
122 - builder.append(customerId);  
123 builder.append(", title="); 165 builder.append(", title=");
124 builder.append(title); 166 builder.append(title);
125 builder.append("]"); 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,6 +18,7 @@ package org.thingsboard.server.common.data.relation;
18 public enum RelationTypeGroup { 18 public enum RelationTypeGroup {
19 19
20 COMMON, 20 COMMON,
21 - ALARM 21 + ALARM,
  22 + DASHBOARD
22 23
23 } 24 }
@@ -97,7 +97,9 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom @@ -97,7 +97,9 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
97 public Customer saveCustomer(Customer customer) { 97 public Customer saveCustomer(Customer customer) {
98 log.trace("Executing saveCustomer [{}]", customer); 98 log.trace("Executing saveCustomer [{}]", customer);
99 customerValidator.validate(customer); 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 @Override 105 @Override
@@ -108,7 +110,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom @@ -108,7 +110,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
108 if (customer == null) { 110 if (customer == null) {
109 throw new IncorrectParameterException("Unable to delete non-existent customer."); 111 throw new IncorrectParameterException("Unable to delete non-existent customer.");
110 } 112 }
111 - dashboardService.unassignCustomerDashboards(customer.getTenantId(), customerId); 113 + dashboardService.unassignCustomerDashboards(customerId);
112 assetService.unassignCustomerAssets(customer.getTenantId(), customerId); 114 assetService.unassignCustomerAssets(customer.getTenantId(), customerId);
113 deviceService.unassignCustomerDevices(customer.getTenantId(), customerId); 115 deviceService.unassignCustomerDevices(customer.getTenantId(), customerId);
114 userService.deleteCustomerUsers(customer.getTenantId(), customerId); 116 userService.deleteCustomerUsers(customer.getTenantId(), customerId);
@@ -15,16 +15,26 @@ @@ -15,16 +15,26 @@
15 */ 15 */
16 package org.thingsboard.server.dao.dashboard; 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 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.beans.factory.annotation.Autowired;
19 import org.springframework.stereotype.Component; 23 import org.springframework.stereotype.Component;
20 import org.thingsboard.server.common.data.DashboardInfo; 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 import org.thingsboard.server.common.data.page.TextPageLink; 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 import org.thingsboard.server.dao.DaoUtil; 31 import org.thingsboard.server.dao.DaoUtil;
23 import org.thingsboard.server.dao.model.nosql.DashboardInfoEntity; 32 import org.thingsboard.server.dao.model.nosql.DashboardInfoEntity;
24 import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; 33 import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
  34 +import org.thingsboard.server.dao.relation.RelationDao;
25 import org.thingsboard.server.dao.util.NoSqlDao; 35 import org.thingsboard.server.dao.util.NoSqlDao;
26 36
27 -import java.util.Arrays; 37 +import java.util.ArrayList;
28 import java.util.Collections; 38 import java.util.Collections;
29 import java.util.List; 39 import java.util.List;
30 import java.util.UUID; 40 import java.util.UUID;
@@ -37,6 +47,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.*; @@ -37,6 +47,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
37 @NoSqlDao 47 @NoSqlDao
38 public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao { 48 public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
39 49
  50 + @Autowired
  51 + private RelationDao relationDao;
  52 +
40 @Override 53 @Override
41 protected Class<DashboardInfoEntity> getColumnFamilyClass() { 54 protected Class<DashboardInfoEntity> getColumnFamilyClass() {
42 return DashboardInfoEntity.class; 55 return DashboardInfoEntity.class;
@@ -59,15 +72,18 @@ public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<Da @@ -59,15 +72,18 @@ public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<Da
59 } 72 }
60 73
61 @Override 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 log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink); 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,8 +15,10 @@
15 */ 15 */
16 package org.thingsboard.server.dao.dashboard; 16 package org.thingsboard.server.dao.dashboard;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 import org.thingsboard.server.common.data.DashboardInfo; 19 import org.thingsboard.server.common.data.DashboardInfo;
19 import org.thingsboard.server.common.data.page.TextPageLink; 20 import org.thingsboard.server.common.data.page.TextPageLink;
  21 +import org.thingsboard.server.common.data.page.TimePageLink;
20 import org.thingsboard.server.dao.Dao; 22 import org.thingsboard.server.dao.Dao;
21 23
22 import java.util.List; 24 import java.util.List;
@@ -44,6 +46,6 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> { @@ -44,6 +46,6 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> {
44 * @param pageLink the page link 46 * @param pageLink the page link
45 * @return the list of dashboard objects 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,6 +23,10 @@ import org.thingsboard.server.common.data.id.DashboardId;
23 import org.thingsboard.server.common.data.id.TenantId; 23 import org.thingsboard.server.common.data.id.TenantId;
24 import org.thingsboard.server.common.data.page.TextPageData; 24 import org.thingsboard.server.common.data.page.TextPageData;
25 import org.thingsboard.server.common.data.page.TextPageLink; 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 public interface DashboardService { 31 public interface DashboardService {
28 32
@@ -38,7 +42,7 @@ public interface DashboardService { @@ -38,7 +42,7 @@ public interface DashboardService {
38 42
39 Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId); 43 Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId);
40 44
41 - Dashboard unassignDashboardFromCustomer(DashboardId dashboardId); 45 + Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId);
42 46
43 void deleteDashboard(DashboardId dashboardId); 47 void deleteDashboard(DashboardId dashboardId);
44 48
@@ -46,8 +50,10 @@ public interface DashboardService { @@ -46,8 +50,10 @@ public interface DashboardService {
46 50
47 void deleteDashboardsByTenantId(TenantId tenantId); 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,30 +15,42 @@
15 */ 15 */
16 package org.thingsboard.server.dao.dashboard; 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 import com.google.common.util.concurrent.ListenableFuture; 20 import com.google.common.util.concurrent.ListenableFuture;
19 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
20 import org.apache.commons.lang3.StringUtils; 22 import org.apache.commons.lang3.StringUtils;
21 import org.springframework.beans.factory.annotation.Autowired; 23 import org.springframework.beans.factory.annotation.Autowired;
22 import org.springframework.stereotype.Service; 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 import org.thingsboard.server.common.data.id.CustomerId; 27 import org.thingsboard.server.common.data.id.CustomerId;
28 import org.thingsboard.server.common.data.id.DashboardId; 28 import org.thingsboard.server.common.data.id.DashboardId;
29 import org.thingsboard.server.common.data.id.TenantId; 29 import org.thingsboard.server.common.data.id.TenantId;
30 import org.thingsboard.server.common.data.page.TextPageData; 30 import org.thingsboard.server.common.data.page.TextPageData;
31 import org.thingsboard.server.common.data.page.TextPageLink; 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 import org.thingsboard.server.dao.customer.CustomerDao; 36 import org.thingsboard.server.dao.customer.CustomerDao;
33 import org.thingsboard.server.dao.entity.AbstractEntityService; 37 import org.thingsboard.server.dao.entity.AbstractEntityService;
34 import org.thingsboard.server.dao.exception.DataValidationException; 38 import org.thingsboard.server.dao.exception.DataValidationException;
35 import org.thingsboard.server.dao.model.ModelConstants; 39 import org.thingsboard.server.dao.model.ModelConstants;
  40 +import org.thingsboard.server.dao.relation.RelationDao;
36 import org.thingsboard.server.dao.service.DataValidator; 41 import org.thingsboard.server.dao.service.DataValidator;
37 import org.thingsboard.server.dao.service.PaginatedRemover; 42 import org.thingsboard.server.dao.service.PaginatedRemover;
  43 +import org.thingsboard.server.dao.service.TimePaginatedRemover;
38 import org.thingsboard.server.dao.service.Validator; 44 import org.thingsboard.server.dao.service.Validator;
39 import org.thingsboard.server.dao.tenant.TenantDao; 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 import java.util.List; 51 import java.util.List;
  52 +import java.util.Set;
  53 +import java.util.concurrent.ExecutionException;
42 54
43 import static org.thingsboard.server.dao.service.Validator.validateId; 55 import static org.thingsboard.server.dao.service.Validator.validateId;
44 56
@@ -59,7 +71,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -59,7 +71,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
59 71
60 @Autowired 72 @Autowired
61 private CustomerDao customerDao; 73 private CustomerDao customerDao;
62 - 74 +
63 @Override 75 @Override
64 public Dashboard findDashboardById(DashboardId dashboardId) { 76 public Dashboard findDashboardById(DashboardId dashboardId) {
65 log.trace("Executing findDashboardById [{}]", dashboardId); 77 log.trace("Executing findDashboardById [{}]", dashboardId);
@@ -98,15 +110,63 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -98,15 +110,63 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
98 @Override 110 @Override
99 public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId) { 111 public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId) {
100 Dashboard dashboard = findDashboardById(dashboardId); 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 @Override 133 @Override
106 - public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId) { 134 + public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId) {
107 Dashboard dashboard = findDashboardById(dashboardId); 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 @Override 172 @Override
@@ -134,23 +194,44 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -134,23 +194,44 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
134 } 194 }
135 195
136 @Override 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 log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); 198 log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink);
139 Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); 199 Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
140 Validator.validateId(customerId, "Incorrect customerId " + customerId); 200 Validator.validateId(customerId, "Incorrect customerId " + customerId);
141 Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); 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 @Override 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 Validator.validateId(customerId, "Incorrect customerId " + customerId); 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 private DataValidator<Dashboard> dashboardValidator = 235 private DataValidator<Dashboard> dashboardValidator =
155 new DataValidator<Dashboard>() { 236 new DataValidator<Dashboard>() {
156 @Override 237 @Override
@@ -166,17 +247,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -166,17 +247,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
166 throw new DataValidationException("Dashboard is referencing to non-existent tenant!"); 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,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 @Override 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 @Override 285 @Override
211 protected void removeEntity(DashboardInfo entity) { 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,13 +266,11 @@ public class ModelConstants {
266 */ 266 */
267 public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard"; 267 public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard";
268 public static final String DASHBOARD_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; 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 public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY; 269 public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY;
271 public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration"; 270 public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration";
  271 + public static final String DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY = "assigned_customers";
272 272
273 public static final String DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_tenant_and_search_text"; 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 * Cassandra plugin metadata constants. 276 * Cassandra plugin metadata constants.
@@ -19,16 +19,23 @@ import com.datastax.driver.core.utils.UUIDs; @@ -19,16 +19,23 @@ import com.datastax.driver.core.utils.UUIDs;
19 import com.datastax.driver.mapping.annotations.Column; 19 import com.datastax.driver.mapping.annotations.Column;
20 import com.datastax.driver.mapping.annotations.PartitionKey; 20 import com.datastax.driver.mapping.annotations.PartitionKey;
21 import com.datastax.driver.mapping.annotations.Table; 21 import com.datastax.driver.mapping.annotations.Table;
  22 +import com.fasterxml.jackson.core.JsonProcessingException;
  23 +import com.fasterxml.jackson.databind.JavaType;
22 import com.fasterxml.jackson.databind.JsonNode; 24 import com.fasterxml.jackson.databind.JsonNode;
  25 +import com.fasterxml.jackson.databind.ObjectMapper;
23 import lombok.EqualsAndHashCode; 26 import lombok.EqualsAndHashCode;
24 import lombok.ToString; 27 import lombok.ToString;
  28 +import lombok.extern.slf4j.Slf4j;
  29 +import org.springframework.util.StringUtils;
25 import org.thingsboard.server.common.data.Dashboard; 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 import org.thingsboard.server.common.data.id.DashboardId; 32 import org.thingsboard.server.common.data.id.DashboardId;
28 import org.thingsboard.server.common.data.id.TenantId; 33 import org.thingsboard.server.common.data.id.TenantId;
29 import org.thingsboard.server.dao.model.SearchTextEntity; 34 import org.thingsboard.server.dao.model.SearchTextEntity;
30 import org.thingsboard.server.dao.model.type.JsonCodec; 35 import org.thingsboard.server.dao.model.type.JsonCodec;
31 36
  37 +import java.io.IOException;
  38 +import java.util.HashSet;
32 import java.util.UUID; 39 import java.util.UUID;
33 40
34 import static org.thingsboard.server.dao.model.ModelConstants.*; 41 import static org.thingsboard.server.dao.model.ModelConstants.*;
@@ -36,8 +43,13 @@ import static org.thingsboard.server.dao.model.ModelConstants.*; @@ -36,8 +43,13 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
36 @Table(name = DASHBOARD_COLUMN_FAMILY_NAME) 43 @Table(name = DASHBOARD_COLUMN_FAMILY_NAME)
37 @EqualsAndHashCode 44 @EqualsAndHashCode
38 @ToString 45 @ToString
  46 +@Slf4j
39 public final class DashboardEntity implements SearchTextEntity<Dashboard> { 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 @PartitionKey(value = 0) 53 @PartitionKey(value = 0)
42 @Column(name = ID_PROPERTY) 54 @Column(name = ID_PROPERTY)
43 private UUID id; 55 private UUID id;
@@ -46,16 +58,15 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -46,16 +58,15 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
46 @Column(name = DASHBOARD_TENANT_ID_PROPERTY) 58 @Column(name = DASHBOARD_TENANT_ID_PROPERTY)
47 private UUID tenantId; 59 private UUID tenantId;
48 60
49 - @PartitionKey(value = 2)  
50 - @Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)  
51 - private UUID customerId;  
52 -  
53 @Column(name = DASHBOARD_TITLE_PROPERTY) 61 @Column(name = DASHBOARD_TITLE_PROPERTY)
54 private String title; 62 private String title;
55 63
56 @Column(name = SEARCH_TEXT_PROPERTY) 64 @Column(name = SEARCH_TEXT_PROPERTY)
57 private String searchText; 65 private String searchText;
58 - 66 +
  67 + @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  68 + private String assignedCustomers;
  69 +
59 @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class) 70 @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
60 private JsonNode configuration; 71 private JsonNode configuration;
61 72
@@ -70,10 +81,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -70,10 +81,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
70 if (dashboard.getTenantId() != null) { 81 if (dashboard.getTenantId() != null) {
71 this.tenantId = dashboard.getTenantId().getId(); 82 this.tenantId = dashboard.getTenantId().getId();
72 } 83 }
73 - if (dashboard.getCustomerId() != null) {  
74 - this.customerId = dashboard.getCustomerId().getId();  
75 - }  
76 this.title = dashboard.getTitle(); 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 this.configuration = dashboard.getConfiguration(); 92 this.configuration = dashboard.getConfiguration();
78 } 93 }
79 94
@@ -93,14 +108,6 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -93,14 +108,6 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
93 this.tenantId = tenantId; 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 public String getTitle() { 111 public String getTitle() {
105 return title; 112 return title;
106 } 113 }
@@ -109,6 +116,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -109,6 +116,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
109 this.title = title; 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 public JsonNode getConfiguration() { 127 public JsonNode getConfiguration() {
113 return configuration; 128 return configuration;
114 } 129 }
@@ -138,10 +153,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -138,10 +153,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
138 if (tenantId != null) { 153 if (tenantId != null) {
139 dashboard.setTenantId(new TenantId(tenantId)); 154 dashboard.setTenantId(new TenantId(tenantId));
140 } 155 }
141 - if (customerId != null) {  
142 - dashboard.setCustomerId(new CustomerId(customerId));  
143 - }  
144 dashboard.setTitle(title); 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 dashboard.setConfiguration(configuration); 164 dashboard.setConfiguration(configuration);
146 return dashboard; 165 return dashboard;
147 } 166 }
@@ -19,14 +19,21 @@ import com.datastax.driver.core.utils.UUIDs; @@ -19,14 +19,21 @@ import com.datastax.driver.core.utils.UUIDs;
19 import com.datastax.driver.mapping.annotations.Column; 19 import com.datastax.driver.mapping.annotations.Column;
20 import com.datastax.driver.mapping.annotations.PartitionKey; 20 import com.datastax.driver.mapping.annotations.PartitionKey;
21 import com.datastax.driver.mapping.annotations.Table; 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 import lombok.EqualsAndHashCode; 25 import lombok.EqualsAndHashCode;
23 import lombok.ToString; 26 import lombok.ToString;
  27 +import lombok.extern.slf4j.Slf4j;
  28 +import org.springframework.util.StringUtils;
24 import org.thingsboard.server.common.data.DashboardInfo; 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 import org.thingsboard.server.common.data.id.DashboardId; 31 import org.thingsboard.server.common.data.id.DashboardId;
27 import org.thingsboard.server.common.data.id.TenantId; 32 import org.thingsboard.server.common.data.id.TenantId;
28 import org.thingsboard.server.dao.model.SearchTextEntity; 33 import org.thingsboard.server.dao.model.SearchTextEntity;
29 34
  35 +import java.io.IOException;
  36 +import java.util.HashSet;
30 import java.util.UUID; 37 import java.util.UUID;
31 38
32 import static org.thingsboard.server.dao.model.ModelConstants.*; 39 import static org.thingsboard.server.dao.model.ModelConstants.*;
@@ -34,8 +41,13 @@ import static org.thingsboard.server.dao.model.ModelConstants.*; @@ -34,8 +41,13 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
34 @Table(name = DASHBOARD_COLUMN_FAMILY_NAME) 41 @Table(name = DASHBOARD_COLUMN_FAMILY_NAME)
35 @EqualsAndHashCode 42 @EqualsAndHashCode
36 @ToString 43 @ToString
  44 +@Slf4j
37 public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { 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 @PartitionKey(value = 0) 51 @PartitionKey(value = 0)
40 @Column(name = ID_PROPERTY) 52 @Column(name = ID_PROPERTY)
41 private UUID id; 53 private UUID id;
@@ -44,16 +56,15 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -44,16 +56,15 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
44 @Column(name = DASHBOARD_TENANT_ID_PROPERTY) 56 @Column(name = DASHBOARD_TENANT_ID_PROPERTY)
45 private UUID tenantId; 57 private UUID tenantId;
46 58
47 - @PartitionKey(value = 2)  
48 - @Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)  
49 - private UUID customerId;  
50 -  
51 @Column(name = DASHBOARD_TITLE_PROPERTY) 59 @Column(name = DASHBOARD_TITLE_PROPERTY)
52 private String title; 60 private String title;
53 61
54 @Column(name = SEARCH_TEXT_PROPERTY) 62 @Column(name = SEARCH_TEXT_PROPERTY)
55 private String searchText; 63 private String searchText;
56 64
  65 + @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  66 + private String assignedCustomers;
  67 +
57 public DashboardInfoEntity() { 68 public DashboardInfoEntity() {
58 super(); 69 super();
59 } 70 }
@@ -65,10 +76,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -65,10 +76,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
65 if (dashboardInfo.getTenantId() != null) { 76 if (dashboardInfo.getTenantId() != null) {
66 this.tenantId = dashboardInfo.getTenantId().getId(); 77 this.tenantId = dashboardInfo.getTenantId().getId();
67 } 78 }
68 - if (dashboardInfo.getCustomerId() != null) {  
69 - this.customerId = dashboardInfo.getCustomerId().getId();  
70 - }  
71 this.title = dashboardInfo.getTitle(); 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 public UUID getId() { 89 public UUID getId() {
@@ -87,14 +102,6 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -87,14 +102,6 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
87 this.tenantId = tenantId; 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 public String getTitle() { 105 public String getTitle() {
99 return title; 106 return title;
100 } 107 }
@@ -103,6 +110,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -103,6 +110,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
103 this.title = title; 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 @Override 121 @Override
107 public String getSearchTextSource() { 122 public String getSearchTextSource() {
108 return getTitle(); 123 return getTitle();
@@ -124,10 +139,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -124,10 +139,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
124 if (tenantId != null) { 139 if (tenantId != null) {
125 dashboardInfo.setTenantId(new TenantId(tenantId)); 140 dashboardInfo.setTenantId(new TenantId(tenantId));
126 } 141 }
127 - if (customerId != null) {  
128 - dashboardInfo.setCustomerId(new CustomerId(customerId));  
129 - }  
130 dashboardInfo.setTitle(title); 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 return dashboardInfo; 150 return dashboardInfo;
132 } 151 }
133 152
@@ -16,13 +16,18 @@ @@ -16,13 +16,18 @@
16 package org.thingsboard.server.dao.model.sql; 16 package org.thingsboard.server.dao.model.sql;
17 17
18 import com.datastax.driver.core.utils.UUIDs; 18 import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.core.JsonProcessingException;
  20 +import com.fasterxml.jackson.databind.JavaType;
19 import com.fasterxml.jackson.databind.JsonNode; 21 import com.fasterxml.jackson.databind.JsonNode;
  22 +import com.fasterxml.jackson.databind.ObjectMapper;
20 import lombok.Data; 23 import lombok.Data;
21 import lombok.EqualsAndHashCode; 24 import lombok.EqualsAndHashCode;
  25 +import lombok.extern.slf4j.Slf4j;
22 import org.hibernate.annotations.Type; 26 import org.hibernate.annotations.Type;
23 import org.hibernate.annotations.TypeDef; 27 import org.hibernate.annotations.TypeDef;
  28 +import org.springframework.util.StringUtils;
24 import org.thingsboard.server.common.data.Dashboard; 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 import org.thingsboard.server.common.data.id.DashboardId; 31 import org.thingsboard.server.common.data.id.DashboardId;
27 import org.thingsboard.server.common.data.id.TenantId; 32 import org.thingsboard.server.common.data.id.TenantId;
28 import org.thingsboard.server.dao.model.BaseSqlEntity; 33 import org.thingsboard.server.dao.model.BaseSqlEntity;
@@ -33,26 +38,33 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; @@ -33,26 +38,33 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType;
33 import javax.persistence.Column; 38 import javax.persistence.Column;
34 import javax.persistence.Entity; 39 import javax.persistence.Entity;
35 import javax.persistence.Table; 40 import javax.persistence.Table;
  41 +import java.io.IOException;
  42 +import java.util.HashSet;
36 43
37 @Data 44 @Data
  45 +@Slf4j
38 @EqualsAndHashCode(callSuper = true) 46 @EqualsAndHashCode(callSuper = true)
39 @Entity 47 @Entity
40 @TypeDef(name = "json", typeClass = JsonStringType.class) 48 @TypeDef(name = "json", typeClass = JsonStringType.class)
41 @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME) 49 @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
42 public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements SearchTextEntity<Dashboard> { 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 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY) 56 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
45 private String tenantId; 57 private String tenantId;
46 58
47 - @Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)  
48 - private String customerId;  
49 -  
50 @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY) 59 @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
51 private String title; 60 private String title;
52 61
53 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) 62 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
54 private String searchText; 63 private String searchText;
55 64
  65 + @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  66 + private String assignedCustomers;
  67 +
56 @Type(type = "json") 68 @Type(type = "json")
57 @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY) 69 @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY)
58 private JsonNode configuration; 70 private JsonNode configuration;
@@ -68,10 +80,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S @@ -68,10 +80,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
68 if (dashboard.getTenantId() != null) { 80 if (dashboard.getTenantId() != null) {
69 this.tenantId = toString(dashboard.getTenantId().getId()); 81 this.tenantId = toString(dashboard.getTenantId().getId());
70 } 82 }
71 - if (dashboard.getCustomerId() != null) {  
72 - this.customerId = toString(dashboard.getCustomerId().getId());  
73 - }  
74 this.title = dashboard.getTitle(); 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 this.configuration = dashboard.getConfiguration(); 91 this.configuration = dashboard.getConfiguration();
76 } 92 }
77 93
@@ -92,10 +108,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S @@ -92,10 +108,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
92 if (tenantId != null) { 108 if (tenantId != null) {
93 dashboard.setTenantId(new TenantId(toUUID(tenantId))); 109 dashboard.setTenantId(new TenantId(toUUID(tenantId)));
94 } 110 }
95 - if (customerId != null) {  
96 - dashboard.setCustomerId(new CustomerId(toUUID(customerId)));  
97 - }  
98 dashboard.setTitle(title); 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 dashboard.setConfiguration(configuration); 119 dashboard.setConfiguration(configuration);
100 return dashboard; 120 return dashboard;
101 } 121 }
@@ -16,10 +16,15 @@ @@ -16,10 +16,15 @@
16 package org.thingsboard.server.dao.model.sql; 16 package org.thingsboard.server.dao.model.sql;
17 17
18 import com.datastax.driver.core.utils.UUIDs; 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 import lombok.Data; 22 import lombok.Data;
20 import lombok.EqualsAndHashCode; 23 import lombok.EqualsAndHashCode;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.springframework.util.StringUtils;
21 import org.thingsboard.server.common.data.DashboardInfo; 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 import org.thingsboard.server.common.data.id.DashboardId; 28 import org.thingsboard.server.common.data.id.DashboardId;
24 import org.thingsboard.server.common.data.id.TenantId; 29 import org.thingsboard.server.common.data.id.TenantId;
25 import org.thingsboard.server.dao.model.BaseSqlEntity; 30 import org.thingsboard.server.dao.model.BaseSqlEntity;
@@ -29,25 +34,32 @@ import org.thingsboard.server.dao.model.SearchTextEntity; @@ -29,25 +34,32 @@ import org.thingsboard.server.dao.model.SearchTextEntity;
29 import javax.persistence.Column; 34 import javax.persistence.Column;
30 import javax.persistence.Entity; 35 import javax.persistence.Entity;
31 import javax.persistence.Table; 36 import javax.persistence.Table;
  37 +import java.io.IOException;
  38 +import java.util.HashSet;
32 39
33 @Data 40 @Data
  41 +@Slf4j
34 @EqualsAndHashCode(callSuper = true) 42 @EqualsAndHashCode(callSuper = true)
35 @Entity 43 @Entity
36 @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME) 44 @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
37 public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements SearchTextEntity<DashboardInfo> { 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 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY) 51 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
40 private String tenantId; 52 private String tenantId;
41 53
42 - @Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)  
43 - private String customerId;  
44 -  
45 @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY) 54 @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
46 private String title; 55 private String title;
47 56
48 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) 57 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
49 private String searchText; 58 private String searchText;
50 59
  60 + @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  61 + private String assignedCustomers;
  62 +
51 public DashboardInfoEntity() { 63 public DashboardInfoEntity() {
52 super(); 64 super();
53 } 65 }
@@ -59,10 +71,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements @@ -59,10 +71,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
59 if (dashboardInfo.getTenantId() != null) { 71 if (dashboardInfo.getTenantId() != null) {
60 this.tenantId = toString(dashboardInfo.getTenantId().getId()); 72 this.tenantId = toString(dashboardInfo.getTenantId().getId());
61 } 73 }
62 - if (dashboardInfo.getCustomerId() != null) {  
63 - this.customerId = toString(dashboardInfo.getCustomerId().getId());  
64 - }  
65 this.title = dashboardInfo.getTitle(); 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 @Override 84 @Override
@@ -86,10 +102,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements @@ -86,10 +102,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
86 if (tenantId != null) { 102 if (tenantId != null) {
87 dashboardInfo.setTenantId(new TenantId(toUUID(tenantId))); 103 dashboardInfo.setTenantId(new TenantId(toUUID(tenantId)));
88 } 104 }
89 - if (customerId != null) {  
90 - dashboardInfo.setCustomerId(new CustomerId(toUUID(customerId)));  
91 - }  
92 dashboardInfo.setTitle(title); 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 return dashboardInfo; 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,6 +17,7 @@ package org.thingsboard.server.dao.service;
17 17
18 import org.thingsboard.server.common.data.id.EntityId; 18 import org.thingsboard.server.common.data.id.EntityId;
19 import org.thingsboard.server.common.data.id.UUIDBased; 19 import org.thingsboard.server.common.data.id.UUIDBased;
  20 +import org.thingsboard.server.common.data.page.BasePageLink;
20 import org.thingsboard.server.common.data.page.TextPageLink; 21 import org.thingsboard.server.common.data.page.TextPageLink;
21 import org.thingsboard.server.dao.exception.IncorrectParameterException; 22 import org.thingsboard.server.dao.exception.IncorrectParameterException;
22 23
@@ -116,7 +117,7 @@ public class Validator { @@ -116,7 +117,7 @@ public class Validator {
116 * @param pageLink the page link 117 * @param pageLink the page link
117 * @param errorMessage the error message for exception 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 if (pageLink == null || pageLink.getLimit() < 1 || (pageLink.getIdOffset() != null && pageLink.getIdOffset().version() != 1)) { 121 if (pageLink == null || pageLink.getLimit() < 1 || (pageLink.getIdOffset() != null && pageLink.getIdOffset().version() != 1)) {
121 throw new IncorrectParameterException(errorMessage); 122 throw new IncorrectParameterException(errorMessage);
122 } 123 }
@@ -39,12 +39,4 @@ public interface DashboardInfoRepository extends CrudRepository<DashboardInfoEnt @@ -39,12 +39,4 @@ public interface DashboardInfoRepository extends CrudRepository<DashboardInfoEnt
39 @Param("idOffset") String idOffset, 39 @Param("idOffset") String idOffset,
40 Pageable pageable); 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,19 +15,31 @@
15 */ 15 */
16 package org.thingsboard.server.dao.sql.dashboard; 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 import org.springframework.beans.factory.annotation.Autowired; 22 import org.springframework.beans.factory.annotation.Autowired;
19 import org.springframework.data.domain.PageRequest; 23 import org.springframework.data.domain.PageRequest;
20 import org.springframework.data.repository.CrudRepository; 24 import org.springframework.data.repository.CrudRepository;
21 import org.springframework.stereotype.Component; 25 import org.springframework.stereotype.Component;
22 import org.thingsboard.server.common.data.DashboardInfo; 26 import org.thingsboard.server.common.data.DashboardInfo;
  27 +import org.thingsboard.server.common.data.EntityType;
23 import org.thingsboard.server.common.data.UUIDConverter; 28 import org.thingsboard.server.common.data.UUIDConverter;
  29 +import org.thingsboard.server.common.data.id.CustomerId;
24 import org.thingsboard.server.common.data.page.TextPageLink; 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 import org.thingsboard.server.dao.DaoUtil; 34 import org.thingsboard.server.dao.DaoUtil;
26 import org.thingsboard.server.dao.dashboard.DashboardInfoDao; 35 import org.thingsboard.server.dao.dashboard.DashboardInfoDao;
27 import org.thingsboard.server.dao.model.sql.DashboardInfoEntity; 36 import org.thingsboard.server.dao.model.sql.DashboardInfoEntity;
  37 +import org.thingsboard.server.dao.relation.RelationDao;
28 import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; 38 import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
29 import org.thingsboard.server.dao.util.SqlDao; 39 import org.thingsboard.server.dao.util.SqlDao;
30 40
  41 +import java.sql.Time;
  42 +import java.util.ArrayList;
31 import java.util.List; 43 import java.util.List;
32 import java.util.Objects; 44 import java.util.Objects;
33 import java.util.UUID; 45 import java.util.UUID;
@@ -37,11 +49,15 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR; @@ -37,11 +49,15 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
37 /** 49 /**
38 * Created by Valerii Sosliuk on 5/6/2017. 50 * Created by Valerii Sosliuk on 5/6/2017.
39 */ 51 */
  52 +@Slf4j
40 @Component 53 @Component
41 @SqlDao 54 @SqlDao
42 public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao { 55 public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
43 56
44 @Autowired 57 @Autowired
  58 + private RelationDao relationDao;
  59 +
  60 + @Autowired
45 private DashboardInfoRepository dashboardInfoRepository; 61 private DashboardInfoRepository dashboardInfoRepository;
46 62
47 @Override 63 @Override
@@ -65,13 +81,17 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE @@ -65,13 +81,17 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
65 } 81 }
66 82
67 @Override 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,26 +364,19 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.widget_type_by_tenant_and_ali
364 CREATE TABLE IF NOT EXISTS thingsboard.dashboard ( 364 CREATE TABLE IF NOT EXISTS thingsboard.dashboard (
365 id timeuuid, 365 id timeuuid,
366 tenant_id timeuuid, 366 tenant_id timeuuid,
367 - customer_id timeuuid,  
368 title text, 367 title text,
369 search_text text, 368 search_text text,
  369 + assigned_customers text,
370 configuration text, 370 configuration text,
371 - PRIMARY KEY (id, tenant_id, customer_id) 371 + PRIMARY KEY (id, tenant_id)
372 ); 372 );
373 373
374 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS 374 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
375 SELECT * 375 SELECT *
376 from thingsboard.dashboard 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 CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf ( 381 CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf (
389 entity_type text, // (DEVICE, CUSTOMER, TENANT) 382 entity_type text, // (DEVICE, CUSTOMER, TENANT)
@@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS customer ( @@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS customer (
105 CREATE TABLE IF NOT EXISTS dashboard ( 105 CREATE TABLE IF NOT EXISTS dashboard (
106 id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY, 106 id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
107 configuration varchar(10000000), 107 configuration varchar(10000000),
108 - customer_id varchar(31), 108 + assigned_customers varchar(1000000),
109 search_text varchar(255), 109 search_text varchar(255),
110 tenant_id varchar(31), 110 tenant_id varchar(31),
111 title varchar(255) 111 title varchar(255)
@@ -29,13 +29,17 @@ import org.thingsboard.server.common.data.id.CustomerId; @@ -29,13 +29,17 @@ import org.thingsboard.server.common.data.id.CustomerId;
29 import org.thingsboard.server.common.data.id.TenantId; 29 import org.thingsboard.server.common.data.id.TenantId;
30 import org.thingsboard.server.common.data.page.TextPageData; 30 import org.thingsboard.server.common.data.page.TextPageData;
31 import org.thingsboard.server.common.data.page.TextPageLink; 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 import org.thingsboard.server.dao.exception.DataValidationException; 34 import org.thingsboard.server.dao.exception.DataValidationException;
33 import org.thingsboard.server.dao.model.ModelConstants; 35 import org.thingsboard.server.dao.model.ModelConstants;
34 36
35 import java.io.IOException; 37 import java.io.IOException;
  38 +import java.sql.Time;
36 import java.util.ArrayList; 39 import java.util.ArrayList;
37 import java.util.Collections; 40 import java.util.Collections;
38 import java.util.List; 41 import java.util.List;
  42 +import java.util.concurrent.ExecutionException;
39 43
40 public abstract class BaseDashboardServiceTest extends AbstractServiceTest { 44 public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
41 45
@@ -68,8 +72,6 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { @@ -68,8 +72,6 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
68 Assert.assertNotNull(savedDashboard.getId()); 72 Assert.assertNotNull(savedDashboard.getId());
69 Assert.assertTrue(savedDashboard.getCreatedTime() > 0); 73 Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
70 Assert.assertEquals(dashboard.getTenantId(), savedDashboard.getTenantId()); 74 Assert.assertEquals(dashboard.getTenantId(), savedDashboard.getTenantId());
71 - Assert.assertNotNull(savedDashboard.getCustomerId());  
72 - Assert.assertEquals(ModelConstants.NULL_UUID, savedDashboard.getCustomerId().getId());  
73 Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle()); 75 Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
74 76
75 savedDashboard.setTitle("My new dashboard"); 77 savedDashboard.setTitle("My new dashboard");
@@ -280,7 +282,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { @@ -280,7 +282,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
280 } 282 }
281 283
282 @Test 284 @Test
283 - public void testFindDashboardsByTenantIdAndCustomerId() { 285 + public void testFindDashboardsByTenantIdAndCustomerId() throws ExecutionException, InterruptedException {
284 Tenant tenant = new Tenant(); 286 Tenant tenant = new Tenant();
285 tenant.setTitle("Test tenant"); 287 tenant.setTitle("Test tenant");
286 tenant = tenantService.saveTenant(tenant); 288 tenant = tenantService.saveTenant(tenant);
@@ -303,10 +305,10 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { @@ -303,10 +305,10 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
303 } 305 }
304 306
305 List<DashboardInfo> loadedDashboards = new ArrayList<>(); 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 do { 310 do {
309 - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink); 311 + pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
310 loadedDashboards.addAll(pageData.getData()); 312 loadedDashboards.addAll(pageData.getData());
311 if (pageData.hasNext()) { 313 if (pageData.hasNext()) {
312 pageLink = pageData.getNextPageLink(); 314 pageLink = pageData.getNextPageLink();
@@ -318,98 +320,14 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { @@ -318,98 +320,14 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
318 320
319 Assert.assertEquals(dashboards, loadedDashboards); 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 Assert.assertFalse(pageData.hasNext()); 327 Assert.assertFalse(pageData.hasNext());
326 Assert.assertTrue(pageData.getData().isEmpty()); 328 Assert.assertTrue(pageData.getData().isEmpty());
327 329
328 tenantService.deleteTenant(tenantId); 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,6 +16,7 @@
16 package org.thingsboard.server.dao.sql.dashboard; 16 package org.thingsboard.server.dao.sql.dashboard;
17 17
18 import com.datastax.driver.core.utils.UUIDs; 18 import com.datastax.driver.core.utils.UUIDs;
  19 +import org.junit.Assert;
19 import org.junit.Test; 20 import org.junit.Test;
20 import org.springframework.beans.factory.annotation.Autowired; 21 import org.springframework.beans.factory.annotation.Autowired;
21 import org.thingsboard.server.common.data.DashboardInfo; 22 import org.thingsboard.server.common.data.DashboardInfo;
@@ -40,53 +41,26 @@ public class JpaDashboardInfoDaoTest extends AbstractJpaDaoTest { @@ -40,53 +41,26 @@ public class JpaDashboardInfoDaoTest extends AbstractJpaDaoTest {
40 @Test 41 @Test
41 public void testFindDashboardsByTenantId() { 42 public void testFindDashboardsByTenantId() {
42 UUID tenantId1 = UUIDs.timeBased(); 43 UUID tenantId1 = UUIDs.timeBased();
43 - UUID customerId1 = UUIDs.timeBased();  
44 UUID tenantId2 = UUIDs.timeBased(); 44 UUID tenantId2 = UUIDs.timeBased();
45 - UUID customerId2 = UUIDs.timeBased();  
46 45
47 for (int i = 0; i < 20; i++) { 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 TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD"); 51 TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD");
53 List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink1); 52 List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink1);
54 - assertEquals(15, dashboardInfos1.size()); 53 + Assert.assertEquals(15, dashboardInfos1.size());
55 54
56 TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null); 55 TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null);
57 List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink2); 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 DashboardInfo dashboardInfo = new DashboardInfo(); 61 DashboardInfo dashboardInfo = new DashboardInfo();
87 dashboardInfo.setId(new DashboardId(UUIDs.timeBased())); 62 dashboardInfo.setId(new DashboardId(UUIDs.timeBased()));
88 dashboardInfo.setTenantId(new TenantId(tenantId)); 63 dashboardInfo.setTenantId(new TenantId(tenantId));
89 - dashboardInfo.setCustomerId(new CustomerId(customerId));  
90 dashboardInfo.setTitle("DASHBOARD_" + index); 64 dashboardInfo.setTitle("DASHBOARD_" + index);
91 dashboardInfoDao.save(dashboardInfo); 65 dashboardInfoDao.save(dashboardInfo);
92 } 66 }
@@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.dashboard', []) @@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.dashboard', [])
17 .factory('dashboardService', DashboardService).name; 17 .factory('dashboardService', DashboardService).name;
18 18
19 /*@ngInject*/ 19 /*@ngInject*/
20 -function DashboardService($rootScope, $http, $q, $location, customerService) { 20 +function DashboardService($rootScope, $http, $q, $location, $filter) {
21 21
22 var stDiffPromise; 22 var stDiffPromise;
23 23
@@ -37,7 +37,11 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -37,7 +37,11 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
37 deleteDashboard: deleteDashboard, 37 deleteDashboard: deleteDashboard,
38 saveDashboard: saveDashboard, 38 saveDashboard: saveDashboard,
39 unassignDashboardFromCustomer: unassignDashboardFromCustomer, 39 unassignDashboardFromCustomer: unassignDashboardFromCustomer,
  40 + updateDashboardCustomers: updateDashboardCustomers,
  41 + addDashboardCustomers: addDashboardCustomers,
  42 + removeDashboardCustomers: removeDashboardCustomers,
40 makeDashboardPublic: makeDashboardPublic, 43 makeDashboardPublic: makeDashboardPublic,
  44 + makeDashboardPrivate: makeDashboardPrivate,
41 getPublicDashboardLink: getPublicDashboardLink 45 getPublicDashboardLink: getPublicDashboardLink
42 } 46 }
43 47
@@ -56,14 +60,14 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -56,14 +60,14 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
56 url += '&textOffset=' + pageLink.textOffset; 60 url += '&textOffset=' + pageLink.textOffset;
57 } 61 }
58 $http.get(url, config).then(function success(response) { 62 $http.get(url, config).then(function success(response) {
59 - deferred.resolve(response.data); 63 + deferred.resolve(prepareDashboards(response.data));
60 }, function fail() { 64 }, function fail() {
61 deferred.reject(); 65 deferred.reject();
62 }); 66 });
63 return deferred.promise; 67 return deferred.promise;
64 } 68 }
65 69
66 - function getTenantDashboards(pageLink, applyCustomersInfo, config) { 70 + function getTenantDashboards(pageLink, config) {
67 var deferred = $q.defer(); 71 var deferred = $q.defer();
68 var url = '/api/tenant/dashboards?limit=' + pageLink.limit; 72 var url = '/api/tenant/dashboards?limit=' + pageLink.limit;
69 if (angular.isDefined(pageLink.textSearch)) { 73 if (angular.isDefined(pageLink.textSearch)) {
@@ -76,51 +80,25 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -76,51 +80,25 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
76 url += '&textOffset=' + pageLink.textOffset; 80 url += '&textOffset=' + pageLink.textOffset;
77 } 81 }
78 $http.get(url, config).then(function success(response) { 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 }, function fail() { 84 }, function fail() {
93 deferred.reject(); 85 deferred.reject();
94 }); 86 });
95 return deferred.promise; 87 return deferred.promise;
96 } 88 }
97 89
98 - function getCustomerDashboards(customerId, pageLink, applyCustomersInfo, config) { 90 + function getCustomerDashboards(customerId, pageLink, config) {
99 var deferred = $q.defer(); 91 var deferred = $q.defer();
100 var url = '/api/customer/' + customerId + '/dashboards?limit=' + pageLink.limit; 92 var url = '/api/customer/' + customerId + '/dashboards?limit=' + pageLink.limit;
101 - if (angular.isDefined(pageLink.textSearch)) {  
102 - url += '&textSearch=' + pageLink.textSearch;  
103 - }  
104 if (angular.isDefined(pageLink.idOffset)) { 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 $http.get(url, config).then(function success(response) { 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 }, function fail() { 102 }, function fail() {
125 deferred.reject(); 103 deferred.reject();
126 }); 104 });
@@ -151,7 +129,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -151,7 +129,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
151 var deferred = $q.defer(); 129 var deferred = $q.defer();
152 var url = '/api/dashboard/' + dashboardId; 130 var url = '/api/dashboard/' + dashboardId;
153 $http.get(url, null).then(function success(response) { 131 $http.get(url, null).then(function success(response) {
154 - deferred.resolve(response.data); 132 + deferred.resolve(prepareDashboard(response.data));
155 }, function fail() { 133 }, function fail() {
156 deferred.reject(); 134 deferred.reject();
157 }); 135 });
@@ -162,7 +140,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -162,7 +140,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
162 var deferred = $q.defer(); 140 var deferred = $q.defer();
163 var url = '/api/dashboard/info/' + dashboardId; 141 var url = '/api/dashboard/info/' + dashboardId;
164 $http.get(url, config).then(function success(response) { 142 $http.get(url, config).then(function success(response) {
165 - deferred.resolve(response.data); 143 + deferred.resolve(prepareDashboard(response.data));
166 }, function fail() { 144 }, function fail() {
167 deferred.reject(); 145 deferred.reject();
168 }); 146 });
@@ -172,8 +150,8 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -172,8 +150,8 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
172 function saveDashboard(dashboard) { 150 function saveDashboard(dashboard) {
173 var deferred = $q.defer(); 151 var deferred = $q.defer();
174 var url = '/api/dashboard'; 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 }, function fail() { 155 }, function fail() {
178 deferred.reject(); 156 deferred.reject();
179 }); 157 });
@@ -195,18 +173,51 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -195,18 +173,51 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
195 var deferred = $q.defer(); 173 var deferred = $q.defer();
196 var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId; 174 var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
197 $http.post(url, null).then(function success(response) { 175 $http.post(url, null).then(function success(response) {
198 - deferred.resolve(response.data); 176 + deferred.resolve(prepareDashboard(response.data));
199 }, function fail() { 177 }, function fail() {
200 deferred.reject(); 178 deferred.reject();
201 }); 179 });
202 return deferred.promise; 180 return deferred.promise;
203 } 181 }
204 182
205 - function unassignDashboardFromCustomer(dashboardId) { 183 + function unassignDashboardFromCustomer(customerId, dashboardId) {
206 var deferred = $q.defer(); 184 var deferred = $q.defer();
207 - var url = '/api/customer/dashboard/' + dashboardId; 185 + var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
208 $http.delete(url).then(function success(response) { 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 }, function fail() { 221 }, function fail() {
211 deferred.reject(); 222 deferred.reject();
212 }); 223 });
@@ -217,7 +228,18 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -217,7 +228,18 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
217 var deferred = $q.defer(); 228 var deferred = $q.defer();
218 var url = '/api/customer/public/dashboard/' + dashboardId; 229 var url = '/api/customer/public/dashboard/' + dashboardId;
219 $http.post(url, null).then(function success(response) { 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 }, function fail() { 243 }, function fail() {
222 deferred.reject(); 244 deferred.reject();
223 }); 245 });
@@ -230,8 +252,44 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -230,8 +252,44 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
230 if (port != 80 && port != 443) { 252 if (port != 80 && port != 443) {
231 url += ":" + port; 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 return url; 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,9 +273,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
273 break; 273 break;
274 case types.entityType.dashboard: 274 case types.entityType.dashboard:
275 if (user.authority === 'CUSTOMER_USER') { 275 if (user.authority === 'CUSTOMER_USER') {
276 - promise = dashboardService.getCustomerDashboards(customerId, pageLink, false, config); 276 + promise = dashboardService.getCustomerDashboards(customerId, pageLink, config);
277 } else { 277 } else {
278 - promise = dashboardService.getTenantDashboards(pageLink, false, config); 278 + promise = dashboardService.getTenantDashboards(pageLink, config);
279 } 279 }
280 break; 280 break;
281 case types.entityType.user: 281 case types.entityType.user:
@@ -403,6 +403,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -403,6 +403,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
403 return deferred.promise; 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 function getStateEntityId(filter, stateParams) { 421 function getStateEntityId(filter, stateParams) {
407 var entityId = null; 422 var entityId = null;
408 if (stateParams) { 423 if (stateParams) {
@@ -417,6 +432,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -417,6 +432,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
417 if (!entityId) { 432 if (!entityId) {
418 entityId = filter.defaultStateEntity; 433 entityId = filter.defaultStateEntity;
419 } 434 }
  435 + if (entityId) {
  436 + entityId = resolveAliasEntityId(entityId.entityType, entityId.id);
  437 + }
420 return entityId; 438 return entityId;
421 } 439 }
422 440
@@ -432,7 +450,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -432,7 +450,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
432 var stateEntityId = getStateEntityId(filter, stateParams); 450 var stateEntityId = getStateEntityId(filter, stateParams);
433 switch (filter.type) { 451 switch (filter.type) {
434 case types.aliasFilterType.singleEntity.value: 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 function success(entity) { 455 function success(entity) {
437 result.entities = entitiesToEntitiesInfo([entity]); 456 result.entities = entitiesToEntitiesInfo([entity]);
438 deferred.resolve(result); 457 deferred.resolve(result);
@@ -530,10 +549,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -530,10 +549,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
530 rootEntityId = filter.rootEntity.id; 549 rootEntityId = filter.rootEntity.id;
531 } 550 }
532 if (rootEntityType && rootEntityId) { 551 if (rootEntityType && rootEntityId) {
  552 + var relationQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
533 var searchQuery = { 553 var searchQuery = {
534 parameters: { 554 parameters: {
535 - rootId: rootEntityId,  
536 - rootType: rootEntityType, 555 + rootId: relationQueryRootEntityId.id,
  556 + rootType: relationQueryRootEntityId.entityType,
537 direction: filter.direction 557 direction: filter.direction
538 }, 558 },
539 filters: filter.filters 559 filters: filter.filters
@@ -571,10 +591,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -571,10 +591,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
571 rootEntityId = filter.rootEntity.id; 591 rootEntityId = filter.rootEntity.id;
572 } 592 }
573 if (rootEntityType && rootEntityId) { 593 if (rootEntityType && rootEntityId) {
  594 + var searchQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
574 searchQuery = { 595 searchQuery = {
575 parameters: { 596 parameters: {
576 - rootId: rootEntityId,  
577 - rootType: rootEntityType, 597 + rootId: searchQueryRootEntityId.id,
  598 + rootType: searchQueryRootEntityId.entityType,
578 direction: filter.direction 599 direction: filter.direction
579 }, 600 },
580 relationType: filter.relationType 601 relationType: filter.relationType
@@ -709,7 +730,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -709,7 +730,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
709 return result; 730 return result;
710 } 731 }
711 732
712 - function prepareAllowedEntityTypesList(allowedEntityTypes) { 733 + function prepareAllowedEntityTypesList(allowedEntityTypes, useAliasEntityTypes) {
713 var authority = userService.getAuthority(); 734 var authority = userService.getAuthority();
714 var entityTypes = {}; 735 var entityTypes = {};
715 switch(authority) { 736 switch(authority) {
@@ -726,12 +747,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -726,12 +747,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
726 entityTypes.rule = types.entityType.rule; 747 entityTypes.rule = types.entityType.rule;
727 entityTypes.plugin = types.entityType.plugin; 748 entityTypes.plugin = types.entityType.plugin;
728 entityTypes.dashboard = types.entityType.dashboard; 749 entityTypes.dashboard = types.entityType.dashboard;
  750 + if (useAliasEntityTypes) {
  751 + entityTypes.current_customer = types.aliasEntityType.current_customer;
  752 + }
729 break; 753 break;
730 case 'CUSTOMER_USER': 754 case 'CUSTOMER_USER':
731 entityTypes.device = types.entityType.device; 755 entityTypes.device = types.entityType.device;
732 entityTypes.asset = types.entityType.asset; 756 entityTypes.asset = types.entityType.asset;
733 entityTypes.customer = types.entityType.customer; 757 entityTypes.customer = types.entityType.customer;
734 entityTypes.dashboard = types.entityType.dashboard; 758 entityTypes.dashboard = types.entityType.dashboard;
  759 + if (useAliasEntityTypes) {
  760 + entityTypes.current_customer = types.aliasEntityType.current_customer;
  761 + }
735 break; 762 break;
736 } 763 }
737 764
@@ -266,9 +266,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi @@ -266,9 +266,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
266 var pageLink = {limit: 100}; 266 var pageLink = {limit: 100};
267 var fetchDashboardsPromise; 267 var fetchDashboardsPromise;
268 if (currentUser.authority === 'TENANT_ADMIN') { 268 if (currentUser.authority === 'TENANT_ADMIN') {
269 - fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink, false); 269 + fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink);
270 } else { 270 } else {
271 - fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink, false); 271 + fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink);
272 } 272 }
273 fetchDashboardsPromise.then( 273 fetchDashboardsPromise.then(
274 function success(result) { 274 function success(result) {
@@ -296,6 +296,9 @@ export default angular.module('thingsboard.types', []) @@ -296,6 +296,9 @@ export default angular.module('thingsboard.types', [])
296 dashboard: "DASHBOARD", 296 dashboard: "DASHBOARD",
297 alarm: "ALARM" 297 alarm: "ALARM"
298 }, 298 },
  299 + aliasEntityType: {
  300 + current_customer: "CURRENT_CUSTOMER"
  301 + },
299 entityTypeTranslations: { 302 entityTypeTranslations: {
300 "DEVICE": { 303 "DEVICE": {
301 type: 'entity.type-device', 304 type: 'entity.type-device',
@@ -350,6 +353,10 @@ export default angular.module('thingsboard.types', []) @@ -350,6 +353,10 @@ export default angular.module('thingsboard.types', [])
350 typePlural: 'entity.type-alarms', 353 typePlural: 'entity.type-alarms',
351 list: 'entity.list-of-alarms', 354 list: 'entity.list-of-alarms',
352 nameStartsWith: 'entity.alarm-name-starts-with' 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 entitySearchDirection: { 362 entitySearchDirection: {
@@ -48,7 +48,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u @@ -48,7 +48,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
48 var promise; 48 var promise;
49 if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') { 49 if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
50 if (scope.customerId) { 50 if (scope.customerId) {
51 - promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false, {ignoreLoading: true}); 51 + promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, {ignoreLoading: true});
52 } else { 52 } else {
53 promise = $q.when({data: []}); 53 promise = $q.when({data: []});
54 } 54 }
@@ -60,7 +60,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u @@ -60,7 +60,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
60 promise = $q.when({data: []}); 60 promise = $q.when({data: []});
61 } 61 }
62 } else { 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,12 +48,12 @@ function DashboardSelect($compile, $templateCache, $q, $mdMedia, $mdPanel, $docu
48 var promise; 48 var promise;
49 if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') { 49 if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
50 if (scope.customerId && scope.customerId != types.id.nullUid) { 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 } else { 52 } else {
53 promise = $q.when({data: []}); 53 promise = $q.when({data: []});
54 } 54 }
55 } else { 55 } else {
56 - promise = dashboardService.getTenantDashboards(pageLink, false, {ignoreLoading: true}); 56 + promise = dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true});
57 } 57 }
58 58
59 promise.then(function success(result) { 59 promise.then(function success(result) {
@@ -52,7 +52,7 @@ export default function AddDashboardsToCustomerController(dashboardService, $mdD @@ -52,7 +52,7 @@ export default function AddDashboardsToCustomerController(dashboardService, $mdD
52 fetchMoreItems_: function () { 52 fetchMoreItems_: function () {
53 if (vm.dashboards.hasNext && !vm.dashboards.pending) { 53 if (vm.dashboards.hasNext && !vm.dashboards.pending) {
54 vm.dashboards.pending = true; 54 vm.dashboards.pending = true;
55 - dashboardService.getTenantDashboards(vm.dashboards.nextPageLink, false).then( 55 + dashboardService.getTenantDashboards(vm.dashboards.nextPageLink).then(
56 function success(dashboards) { 56 function success(dashboards) {
57 vm.dashboards.data = vm.dashboards.data.concat(dashboards.data); 57 vm.dashboards.data = vm.dashboards.data.concat(dashboards.data);
58 vm.dashboards.nextPageLink = dashboards.nextPageLink; 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,6 +15,6 @@
15 limitations under the License. 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,24 +19,29 @@
19 ng-show="!isEdit && dashboardScope === 'tenant'" 19 ng-show="!isEdit && dashboardScope === 'tenant'"
20 class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button> 20 class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button>
21 <md-button ng-click="onMakePublic({event: $event})" 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 class="md-raised md-primary">{{ 'dashboard.make-public' | translate }}</md-button> 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 <md-button ng-click="onDeleteDashboard({event: $event})" 34 <md-button ng-click="onDeleteDashboard({event: $event})"
31 ng-show="!isEdit && dashboardScope === 'tenant'" 35 ng-show="!isEdit && dashboardScope === 'tenant'"
32 class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button> 36 class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button>
33 <md-content class="md-padding" layout="column"> 37 <md-content class="md-padding" layout="column">
34 <md-input-container class="md-block" 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 </md-input-container> 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 <tb-social-share-panel style="padding-bottom: 10px;" 45 <tb-social-share-panel style="padding-bottom: 10px;"
41 share-title="{{ 'dashboard.socialshare-title' | translate:{dashboardTitle: dashboard.title} }}" 46 share-title="{{ 'dashboard.socialshare-title' | translate:{dashboardTitle: dashboard.title} }}"
42 share-text="{{ 'dashboard.socialshare-text' | translate:{dashboardTitle: dashboard.title} }}" 47 share-text="{{ 'dashboard.socialshare-text' | translate:{dashboardTitle: dashboard.title} }}"
@@ -20,36 +20,17 @@ import dashboardFieldsetTemplate from './dashboard-fieldset.tpl.html'; @@ -20,36 +20,17 @@ import dashboardFieldsetTemplate from './dashboard-fieldset.tpl.html';
20 /* eslint-enable import/no-unresolved, import/default */ 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 /*@ngInject*/ 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 var linker = function (scope, element) { 24 var linker = function (scope, element) {
25 var template = $templateCache.get(dashboardFieldsetTemplate); 25 var template = $templateCache.get(dashboardFieldsetTemplate);
26 element.html(template); 26 element.html(template);
27 -  
28 - scope.isAssignedToCustomer = false;  
29 - scope.isPublic = false;  
30 - scope.assignedCustomer = null;  
31 scope.publicLink = null; 27 scope.publicLink = null;
32 -  
33 scope.$watch('dashboard', function(newVal) { 28 scope.$watch('dashboard', function(newVal) {
34 if (newVal) { 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 } else { 32 } else {
49 - scope.isAssignedToCustomer = false;  
50 - scope.isPublic = false;  
51 scope.publicLink = null; 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,10 +47,12 @@ export default function DashboardDirective($compile, $templateCache, $translate,
66 scope: { 47 scope: {
67 dashboard: '=', 48 dashboard: '=',
68 isEdit: '=', 49 isEdit: '=',
  50 + customerId: '=',
69 dashboardScope: '=', 51 dashboardScope: '=',
70 theForm: '=', 52 theForm: '=',
71 - onAssignToCustomer: '&',  
72 onMakePublic: '&', 53 onMakePublic: '&',
  54 + onMakePrivate: '&',
  55 + onManageAssignedCustomers: '&',
73 onUnassignFromCustomer: '&', 56 onUnassignFromCustomer: '&',
74 onExportDashboard: '&', 57 onExportDashboard: '&',
75 onDeleteDashboard: '&' 58 onDeleteDashboard: '&'
@@ -17,12 +17,14 @@ @@ -17,12 +17,14 @@
17 17
18 import addDashboardTemplate from './add-dashboard.tpl.html'; 18 import addDashboardTemplate from './add-dashboard.tpl.html';
19 import dashboardCard from './dashboard-card.tpl.html'; 19 import dashboardCard from './dashboard-card.tpl.html';
20 -import assignToCustomerTemplate from './assign-to-customer.tpl.html';  
21 import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.html'; 20 import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.html';
22 import makeDashboardPublicDialogTemplate from './make-dashboard-public-dialog.tpl.html'; 21 import makeDashboardPublicDialogTemplate from './make-dashboard-public-dialog.tpl.html';
  22 +import manageAssignedCustomersTemplate from './manage-assigned-customers.tpl.html';
23 23
24 /* eslint-enable import/no-unresolved, import/default */ 24 /* eslint-enable import/no-unresolved, import/default */
25 25
  26 +import './dashboard-card.scss';
  27 +
26 /*@ngInject*/ 28 /*@ngInject*/
27 export function MakeDashboardPublicDialogController($mdDialog, $translate, toast, dashboardService, dashboard) { 29 export function MakeDashboardPublicDialogController($mdDialog, $translate, toast, dashboardService, dashboard) {
28 30
@@ -48,23 +50,8 @@ export function MakeDashboardPublicDialogController($mdDialog, $translate, toast @@ -48,23 +50,8 @@ export function MakeDashboardPublicDialogController($mdDialog, $translate, toast
48 export function DashboardCardController(types) { 50 export function DashboardCardController(types) {
49 51
50 var vm = this; 52 var vm = this;
51 -  
52 vm.types = types; 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 /*@ngInject*/ 57 /*@ngInject*/
@@ -135,8 +122,9 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -135,8 +122,9 @@ export function DashboardsController(userService, dashboardService, customerServ
135 122
136 vm.dashboardsScope = $state.$current.data.dashboardsType; 123 vm.dashboardsScope = $state.$current.data.dashboardsType;
137 124
138 - vm.assignToCustomer = assignToCustomer;  
139 vm.makePublic = makePublic; 125 vm.makePublic = makePublic;
  126 + vm.makePrivate = makePrivate;
  127 + vm.manageAssignedCustomers = manageAssignedCustomers;
140 vm.unassignFromCustomer = unassignFromCustomer; 128 vm.unassignFromCustomer = unassignFromCustomer;
141 vm.exportDashboard = exportDashboard; 129 vm.exportDashboard = exportDashboard;
142 130
@@ -155,6 +143,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -155,6 +143,7 @@ export function DashboardsController(userService, dashboardService, customerServ
155 } 143 }
156 144
157 if (customerId) { 145 if (customerId) {
  146 + vm.customerId = customerId;
158 vm.customerDashboardsTitle = $translate.instant('customer.dashboards'); 147 vm.customerDashboardsTitle = $translate.instant('customer.dashboards');
159 customerService.getShortCustomerInfo(customerId).then( 148 customerService.getShortCustomerInfo(customerId).then(
160 function success(info) { 149 function success(info) {
@@ -167,7 +156,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -167,7 +156,7 @@ export function DashboardsController(userService, dashboardService, customerServ
167 156
168 if (vm.dashboardsScope === 'tenant') { 157 if (vm.dashboardsScope === 'tenant') {
169 fetchDashboardsFunction = function (pageLink) { 158 fetchDashboardsFunction = function (pageLink) {
170 - return dashboardService.getTenantDashboards(pageLink, true); 159 + return dashboardService.getTenantDashboards(pageLink);
171 }; 160 };
172 deleteDashboardFunction = function (dashboardId) { 161 deleteDashboardFunction = function (dashboardId) {
173 return dashboardService.deleteDashboard(dashboardId); 162 return dashboardService.deleteDashboard(dashboardId);
@@ -194,11 +183,33 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -194,11 +183,33 @@ export function DashboardsController(userService, dashboardService, customerServ
194 details: function() { return $translate.instant('dashboard.make-public') }, 183 details: function() { return $translate.instant('dashboard.make-public') },
195 icon: "share", 184 icon: "share",
196 isEnabled: function(dashboard) { 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 dashboardActionsList.push({ 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 onAction: function ($event, item) { 213 onAction: function ($event, item) {
203 assignToCustomer($event, [ item.id.id ]); 214 assignToCustomer($event, [ item.id.id ]);
204 }, 215 },
@@ -208,8 +219,8 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -208,8 +219,8 @@ export function DashboardsController(userService, dashboardService, customerServ
208 isEnabled: function(dashboard) { 219 isEnabled: function(dashboard) {
209 return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid); 220 return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid);
210 } 221 }
211 - });  
212 - dashboardActionsList.push({ 222 + });*/
  223 + /*dashboardActionsList.push({
213 onAction: function ($event, item) { 224 onAction: function ($event, item) {
214 unassignFromCustomer($event, item, false); 225 unassignFromCustomer($event, item, false);
215 }, 226 },
@@ -219,18 +230,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -219,18 +230,7 @@ export function DashboardsController(userService, dashboardService, customerServ
219 isEnabled: function(dashboard) { 230 isEnabled: function(dashboard) {
220 return dashboard && dashboard.customerId && dashboard.customerId.id !== types.id.nullUid && !dashboard.assignedCustomer.isPublic; 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 dashboardActionsList.push( 235 dashboardActionsList.push(
236 { 236 {
@@ -246,7 +246,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -246,7 +246,7 @@ export function DashboardsController(userService, dashboardService, customerServ
246 dashboardGroupActionsList.push( 246 dashboardGroupActionsList.push(
247 { 247 {
248 onAction: function ($event, items) { 248 onAction: function ($event, items) {
249 - assignDashboardsToCustomer($event, items); 249 + assignDashboardsToCustomers($event, items);
250 }, 250 },
251 name: function() { return $translate.instant('dashboard.assign-dashboards') }, 251 name: function() { return $translate.instant('dashboard.assign-dashboards') },
252 details: function(selectedCount) { 252 details: function(selectedCount) {
@@ -255,6 +255,17 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -255,6 +255,17 @@ export function DashboardsController(userService, dashboardService, customerServ
255 icon: "assignment_ind" 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 dashboardGroupActionsList.push( 270 dashboardGroupActionsList.push(
260 { 271 {
@@ -290,10 +301,10 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -290,10 +301,10 @@ export function DashboardsController(userService, dashboardService, customerServ
290 }); 301 });
291 } else if (vm.dashboardsScope === 'customer' || vm.dashboardsScope === 'customer_user') { 302 } else if (vm.dashboardsScope === 'customer' || vm.dashboardsScope === 'customer_user') {
292 fetchDashboardsFunction = function (pageLink) { 303 fetchDashboardsFunction = function (pageLink) {
293 - return dashboardService.getCustomerDashboards(customerId, pageLink, true); 304 + return dashboardService.getCustomerDashboards(customerId, pageLink);
294 }; 305 };
295 deleteDashboardFunction = function (dashboardId) { 306 deleteDashboardFunction = function (dashboardId) {
296 - return dashboardService.unassignDashboardFromCustomer(dashboardId); 307 + return dashboardService.unassignDashboardFromCustomer(customerId, dashboardId);
297 }; 308 };
298 refreshDashboardsParamsFunction = function () { 309 refreshDashboardsParamsFunction = function () {
299 return {"customerId": customerId, "topIndex": vm.topIndex}; 310 return {"customerId": customerId, "topIndex": vm.topIndex};
@@ -314,26 +325,27 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -314,26 +325,27 @@ export function DashboardsController(userService, dashboardService, customerServ
314 dashboardActionsList.push( 325 dashboardActionsList.push(
315 { 326 {
316 onAction: function ($event, item) { 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 isEnabled: function(dashboard) { 333 isEnabled: function(dashboard) {
323 - return dashboard && !dashboard.assignedCustomer.isPublic; 334 + return dashboard && customerId == dashboard.publicCustomerId;
324 } 335 }
325 } 336 }
326 ); 337 );
  338 +
327 dashboardActionsList.push( 339 dashboardActionsList.push(
328 { 340 {
329 onAction: function ($event, item) { 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 isEnabled: function(dashboard) { 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,7 +353,7 @@ export function DashboardsController(userService, dashboardService, customerServ
341 dashboardGroupActionsList.push( 353 dashboardGroupActionsList.push(
342 { 354 {
343 onAction: function ($event, items) { 355 onAction: function ($event, items) {
344 - unassignDashboardsFromCustomer($event, items); 356 + unassignDashboardsFromCustomer($event, items, customerId);
345 }, 357 },
346 name: function() { return $translate.instant('dashboard.unassign-dashboards') }, 358 name: function() { return $translate.instant('dashboard.unassign-dashboards') },
347 details: function(selectedCount) { 359 details: function(selectedCount) {
@@ -351,7 +363,6 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -351,7 +363,6 @@ export function DashboardsController(userService, dashboardService, customerServ
351 } 363 }
352 ); 364 );
353 365
354 -  
355 vm.dashboardGridConfig.addItemAction = { 366 vm.dashboardGridConfig.addItemAction = {
356 onAction: function ($event) { 367 onAction: function ($event) {
357 addDashboardsToCustomer($event); 368 addDashboardsToCustomer($event);
@@ -428,39 +439,42 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -428,39 +439,42 @@ export function DashboardsController(userService, dashboardService, customerServ
428 return deferred.promise; 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 if ($event) { 463 if ($event) {
433 $event.stopPropagation(); 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 function addDashboardsToCustomer($event) { 480 function addDashboardsToCustomer($event) {
@@ -468,7 +482,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -468,7 +482,7 @@ export function DashboardsController(userService, dashboardService, customerServ
468 $event.stopPropagation(); 482 $event.stopPropagation();
469 } 483 }
470 var pageSize = 10; 484 var pageSize = 10;
471 - dashboardService.getTenantDashboards({limit: pageSize, textSearch: ''}, false).then( 485 + dashboardService.getTenantDashboards({limit: pageSize, textSearch: ''}).then(
472 function success(_dashboards) { 486 function success(_dashboards) {
473 var dashboards = { 487 var dashboards = {
474 pageSize: pageSize, 488 pageSize: pageSize,
@@ -499,30 +513,13 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -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 if ($event) { 517 if ($event) {
512 $event.stopPropagation(); 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 var confirm = $mdDialog.confirm() 523 var confirm = $mdDialog.confirm()
527 .targetEvent($event) 524 .targetEvent($event)
528 .title(title) 525 .title(title)
@@ -531,7 +528,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -531,7 +528,7 @@ export function DashboardsController(userService, dashboardService, customerServ
531 .cancel($translate.instant('action.no')) 528 .cancel($translate.instant('action.no'))
532 .ok($translate.instant('action.yes')); 529 .ok($translate.instant('action.yes'));
533 $mdDialog.show(confirm).then(function () { 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 vm.grid.refreshList(); 532 vm.grid.refreshList();
536 }); 533 });
537 }); 534 });
@@ -556,12 +553,33 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -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 function exportDashboard($event, dashboard) { 577 function exportDashboard($event, dashboard) {
560 $event.stopPropagation(); 578 $event.stopPropagation();
561 importExport.exportDashboard(dashboard.id.id); 579 importExport.exportDashboard(dashboard.id.id);
562 } 580 }
563 581
564 - function unassignDashboardsFromCustomer($event, items) { 582 + function unassignDashboardsFromCustomer($event, items, customerId) {
565 var confirm = $mdDialog.confirm() 583 var confirm = $mdDialog.confirm()
566 .targetEvent($event) 584 .targetEvent($event)
567 .title($translate.instant('dashboard.unassign-dashboards-title', {count: items.selectedCount}, 'messageformat')) 585 .title($translate.instant('dashboard.unassign-dashboards-title', {count: items.selectedCount}, 'messageformat'))
@@ -572,7 +590,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -572,7 +590,7 @@ export function DashboardsController(userService, dashboardService, customerServ
572 $mdDialog.show(confirm).then(function () { 590 $mdDialog.show(confirm).then(function () {
573 var tasks = []; 591 var tasks = [];
574 for (var id in items.selections) { 592 for (var id in items.selections) {
575 - tasks.push(dashboardService.unassignDashboardFromCustomer(id)); 593 + tasks.push(dashboardService.unassignDashboardFromCustomer(customerId, id));
576 } 594 }
577 $q.all(tasks).then(function () { 595 $q.all(tasks).then(function () {
578 vm.grid.refreshList(); 596 vm.grid.refreshList();
@@ -25,10 +25,12 @@ @@ -25,10 +25,12 @@
25 <tb-dashboard-details dashboard="vm.grid.operatingItem()" 25 <tb-dashboard-details dashboard="vm.grid.operatingItem()"
26 is-edit="vm.grid.detailsConfig.isDetailsEditMode" 26 is-edit="vm.grid.detailsConfig.isDetailsEditMode"
27 dashboard-scope="vm.dashboardsScope" 27 dashboard-scope="vm.dashboardsScope"
  28 + customer-id="vm.customerId"
28 the-form="vm.grid.detailsForm" 29 the-form="vm.grid.detailsForm"
29 - on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"  
30 on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)" 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 on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)" 34 on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)"
33 on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details> 35 on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details>
34 </md-tab> 36 </md-tab>
@@ -40,8 +40,8 @@ import DashboardRoutes from './dashboard.routes'; @@ -40,8 +40,8 @@ import DashboardRoutes from './dashboard.routes';
40 import {DashboardsController, DashboardCardController, MakeDashboardPublicDialogController} from './dashboards.controller'; 40 import {DashboardsController, DashboardCardController, MakeDashboardPublicDialogController} from './dashboards.controller';
41 import DashboardController from './dashboard.controller'; 41 import DashboardController from './dashboard.controller';
42 import DashboardSettingsController from './dashboard-settings.controller'; 42 import DashboardSettingsController from './dashboard-settings.controller';
43 -import AssignDashboardToCustomerController from './assign-to-customer.controller';  
44 import AddDashboardsToCustomerController from './add-dashboards-to-customer.controller'; 43 import AddDashboardsToCustomerController from './add-dashboards-to-customer.controller';
  44 +import ManageAssignedCustomersController from './manage-assigned-customers.controller';
45 import AddWidgetController from './add-widget.controller'; 45 import AddWidgetController from './add-widget.controller';
46 import DashboardDirective from './dashboard.directive'; 46 import DashboardDirective from './dashboard.directive';
47 import EditWidgetDirective from './edit-widget.directive'; 47 import EditWidgetDirective from './edit-widget.directive';
@@ -74,8 +74,8 @@ export default angular.module('thingsboard.dashboard', [ @@ -74,8 +74,8 @@ export default angular.module('thingsboard.dashboard', [
74 .controller('MakeDashboardPublicDialogController', MakeDashboardPublicDialogController) 74 .controller('MakeDashboardPublicDialogController', MakeDashboardPublicDialogController)
75 .controller('DashboardController', DashboardController) 75 .controller('DashboardController', DashboardController)
76 .controller('DashboardSettingsController', DashboardSettingsController) 76 .controller('DashboardSettingsController', DashboardSettingsController)
77 - .controller('AssignDashboardToCustomerController', AssignDashboardToCustomerController)  
78 .controller('AddDashboardsToCustomerController', AddDashboardsToCustomerController) 77 .controller('AddDashboardsToCustomerController', AddDashboardsToCustomerController)
  78 + .controller('ManageAssignedCustomersController', ManageAssignedCustomersController)
79 .controller('AddWidgetController', AddWidgetController) 79 .controller('AddWidgetController', AddWidgetController)
80 .directive('tbDashboardDetails', DashboardDirective) 80 .directive('tbDashboardDetails', DashboardDirective)
81 .directive('tbEditWidget', EditWidgetDirective) 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,11 +15,11 @@
15 limitations under the License. 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 <md-toolbar> 20 <md-toolbar>
21 <div class="md-toolbar-tools"> 21 <div class="md-toolbar-tools">
22 - <h2 translate>dashboard.assign-dashboard-to-customer</h2> 22 + <h2 translate>{{vm.titleText}}</h2>
23 <span flex></span> 23 <span flex></span>
24 <md-button class="md-icon-button" ng-click="vm.cancel()"> 24 <md-button class="md-icon-button" ng-click="vm.cancel()">
25 <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> 25 <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
@@ -31,42 +31,17 @@ @@ -31,42 +31,17 @@
31 <md-dialog-content> 31 <md-dialog-content>
32 <div class="md-dialog-content"> 32 <div class="md-dialog-content">
33 <fieldset> 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 </fieldset> 38 </fieldset>
64 </div> 39 </div>
65 </md-dialog-content> 40 </md-dialog-content>
66 <md-dialog-actions layout="row"> 41 <md-dialog-actions layout="row">
67 <span flex></span> 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 </md-button> 45 </md-button>
71 <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | 46 <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
72 translate }} 47 translate }}
@@ -38,7 +38,12 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter @@ -38,7 +38,12 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
38 if (scope.excludeEntityIds && scope.excludeEntityIds.length) { 38 if (scope.excludeEntityIds && scope.excludeEntityIds.length) {
39 limit += scope.excludeEntityIds.length; 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 if (result) { 47 if (result) {
43 if (scope.excludeEntityIds && scope.excludeEntityIds.length) { 48 if (scope.excludeEntityIds && scope.excludeEntityIds.length) {
44 var entities = []; 49 var entities = [];
@@ -71,7 +76,11 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter @@ -71,7 +76,11 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
71 76
72 ngModelCtrl.$render = function () { 77 ngModelCtrl.$render = function () {
73 if (ngModelCtrl.$viewValue) { 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 function success(entity) { 84 function success(entity) {
76 scope.entity = entity; 85 scope.entity = entity;
77 }, 86 },
@@ -114,55 +123,61 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter @@ -114,55 +123,61 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
114 scope.selectEntityText = 'asset.select-asset'; 123 scope.selectEntityText = 'asset.select-asset';
115 scope.entityText = 'asset.asset'; 124 scope.entityText = 'asset.asset';
116 scope.noEntitiesMatchingText = 'asset.no-assets-matching'; 125 scope.noEntitiesMatchingText = 'asset.no-assets-matching';
117 - scope.entityRequiredText = 'asset.asset-required' 126 + scope.entityRequiredText = 'asset.asset-required';
118 break; 127 break;
119 case types.entityType.device: 128 case types.entityType.device:
120 scope.selectEntityText = 'device.select-device'; 129 scope.selectEntityText = 'device.select-device';
121 scope.entityText = 'device.device'; 130 scope.entityText = 'device.device';
122 scope.noEntitiesMatchingText = 'device.no-devices-matching'; 131 scope.noEntitiesMatchingText = 'device.no-devices-matching';
123 - scope.entityRequiredText = 'device.device-required' 132 + scope.entityRequiredText = 'device.device-required';
124 break; 133 break;
125 case types.entityType.rule: 134 case types.entityType.rule:
126 scope.selectEntityText = 'rule.select-rule'; 135 scope.selectEntityText = 'rule.select-rule';
127 scope.entityText = 'rule.rule'; 136 scope.entityText = 'rule.rule';
128 scope.noEntitiesMatchingText = 'rule.no-rules-matching'; 137 scope.noEntitiesMatchingText = 'rule.no-rules-matching';
129 - scope.entityRequiredText = 'rule.rule-required' 138 + scope.entityRequiredText = 'rule.rule-required';
130 break; 139 break;
131 case types.entityType.plugin: 140 case types.entityType.plugin:
132 scope.selectEntityText = 'plugin.select-plugin'; 141 scope.selectEntityText = 'plugin.select-plugin';
133 scope.entityText = 'plugin.plugin'; 142 scope.entityText = 'plugin.plugin';
134 scope.noEntitiesMatchingText = 'plugin.no-plugins-matching'; 143 scope.noEntitiesMatchingText = 'plugin.no-plugins-matching';
135 - scope.entityRequiredText = 'plugin.plugin-required' 144 + scope.entityRequiredText = 'plugin.plugin-required';
136 break; 145 break;
137 case types.entityType.tenant: 146 case types.entityType.tenant:
138 scope.selectEntityText = 'tenant.select-tenant'; 147 scope.selectEntityText = 'tenant.select-tenant';
139 scope.entityText = 'tenant.tenant'; 148 scope.entityText = 'tenant.tenant';
140 scope.noEntitiesMatchingText = 'tenant.no-tenants-matching'; 149 scope.noEntitiesMatchingText = 'tenant.no-tenants-matching';
141 - scope.entityRequiredText = 'tenant.tenant-required' 150 + scope.entityRequiredText = 'tenant.tenant-required';
142 break; 151 break;
143 case types.entityType.customer: 152 case types.entityType.customer:
144 scope.selectEntityText = 'customer.select-customer'; 153 scope.selectEntityText = 'customer.select-customer';
145 scope.entityText = 'customer.customer'; 154 scope.entityText = 'customer.customer';
146 scope.noEntitiesMatchingText = 'customer.no-customers-matching'; 155 scope.noEntitiesMatchingText = 'customer.no-customers-matching';
147 - scope.entityRequiredText = 'customer.customer-required' 156 + scope.entityRequiredText = 'customer.customer-required';
148 break; 157 break;
149 case types.entityType.user: 158 case types.entityType.user:
150 scope.selectEntityText = 'user.select-user'; 159 scope.selectEntityText = 'user.select-user';
151 scope.entityText = 'user.user'; 160 scope.entityText = 'user.user';
152 scope.noEntitiesMatchingText = 'user.no-users-matching'; 161 scope.noEntitiesMatchingText = 'user.no-users-matching';
153 - scope.entityRequiredText = 'user.user-required' 162 + scope.entityRequiredText = 'user.user-required';
154 break; 163 break;
155 case types.entityType.dashboard: 164 case types.entityType.dashboard:
156 scope.selectEntityText = 'dashboard.select-dashboard'; 165 scope.selectEntityText = 'dashboard.select-dashboard';
157 scope.entityText = 'dashboard.dashboard'; 166 scope.entityText = 'dashboard.dashboard';
158 scope.noEntitiesMatchingText = 'dashboard.no-dashboards-matching'; 167 scope.noEntitiesMatchingText = 'dashboard.no-dashboards-matching';
159 - scope.entityRequiredText = 'dashboard.dashboard-required' 168 + scope.entityRequiredText = 'dashboard.dashboard-required';
160 break; 169 break;
161 case types.entityType.alarm: 170 case types.entityType.alarm:
162 scope.selectEntityText = 'alarm.select-alarm'; 171 scope.selectEntityText = 'alarm.select-alarm';
163 scope.entityText = 'alarm.alarm'; 172 scope.entityText = 'alarm.alarm';
164 scope.noEntitiesMatchingText = 'alarm.no-alarms-matching'; 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 break; 181 break;
167 } 182 }
168 if (scope.entity && scope.entity.id.entityType != scope.entityType) { 183 if (scope.entity && scope.entity.id.entityType != scope.entityType) {
@@ -32,6 +32,7 @@ @@ -32,6 +32,7 @@
32 <tb-entity-select flex 32 <tb-entity-select flex
33 the-form="theForm" 33 the-form="theForm"
34 tb-required="true" 34 tb-required="true"
  35 + use-alias-entity-types="true"
35 ng-model="filter.singleEntity"> 36 ng-model="filter.singleEntity">
36 </tb-entity-select> 37 </tb-entity-select>
37 </section> 38 </section>
@@ -78,6 +79,7 @@ @@ -78,6 +79,7 @@
78 <tb-entity-select flex 79 <tb-entity-select flex
79 the-form="theForm" 80 the-form="theForm"
80 tb-required="false" 81 tb-required="false"
  82 + use-alias-entity-types="true"
81 ng-model="filter.defaultStateEntity"> 83 ng-model="filter.defaultStateEntity">
82 </tb-entity-select> 84 </tb-entity-select>
83 </div> 85 </div>
@@ -123,6 +125,7 @@ @@ -123,6 +125,7 @@
123 the-form="theForm" 125 the-form="theForm"
124 tb-required="!filter.rootStateEntity" 126 tb-required="!filter.rootStateEntity"
125 ng-disabled="filter.rootStateEntity" 127 ng-disabled="filter.rootStateEntity"
  128 + use-alias-entity-types="true"
126 ng-model="filter.rootEntity"> 129 ng-model="filter.rootEntity">
127 </tb-entity-select> 130 </tb-entity-select>
128 </div> 131 </div>
@@ -139,6 +142,7 @@ @@ -139,6 +142,7 @@
139 <tb-entity-select flex 142 <tb-entity-select flex
140 the-form="theForm" 143 the-form="theForm"
141 tb-required="false" 144 tb-required="false"
  145 + use-alias-entity-types="true"
142 ng-model="filter.defaultStateEntity"> 146 ng-model="filter.defaultStateEntity">
143 </tb-entity-select> 147 </tb-entity-select>
144 </div> 148 </div>
@@ -182,6 +186,7 @@ @@ -182,6 +186,7 @@
182 the-form="theForm" 186 the-form="theForm"
183 tb-required="!filter.rootStateEntity" 187 tb-required="!filter.rootStateEntity"
184 ng-disabled="filter.rootStateEntity" 188 ng-disabled="filter.rootStateEntity"
  189 + use-alias-entity-types="true"
185 ng-model="filter.rootEntity"> 190 ng-model="filter.rootEntity">
186 </tb-entity-select> 191 </tb-entity-select>
187 </div> 192 </div>
@@ -198,6 +203,7 @@ @@ -198,6 +203,7 @@
198 <tb-entity-select flex 203 <tb-entity-select flex
199 the-form="theForm" 204 the-form="theForm"
200 tb-required="false" 205 tb-required="false"
  206 + use-alias-entity-types="true"
201 ng-model="filter.defaultStateEntity"> 207 ng-model="filter.defaultStateEntity">
202 </tb-entity-select> 208 </tb-entity-select>
203 </div> 209 </div>
@@ -249,6 +255,7 @@ @@ -249,6 +255,7 @@
249 the-form="theForm" 255 the-form="theForm"
250 tb-required="!filter.rootStateEntity" 256 tb-required="!filter.rootStateEntity"
251 ng-disabled="filter.rootStateEntity" 257 ng-disabled="filter.rootStateEntity"
  258 + use-alias-entity-types="true"
252 ng-model="filter.rootEntity"> 259 ng-model="filter.rootEntity">
253 </tb-entity-select> 260 </tb-entity-select>
254 </div> 261 </div>
@@ -265,6 +272,7 @@ @@ -265,6 +272,7 @@
265 <tb-entity-select flex 272 <tb-entity-select flex
266 the-form="theForm" 273 the-form="theForm"
267 tb-required="false" 274 tb-required="false"
  275 + use-alias-entity-types="true"
268 ng-model="filter.defaultStateEntity"> 276 ng-model="filter.defaultStateEntity">
269 </tb-entity-select> 277 </tb-entity-select>
270 </div> 278 </div>
@@ -105,7 +105,8 @@ export default function EntitySelect($compile, $templateCache) { @@ -105,7 +105,8 @@ export default function EntitySelect($compile, $templateCache) {
105 scope: { 105 scope: {
106 theForm: '=?', 106 theForm: '=?',
107 tbRequired: '=?', 107 tbRequired: '=?',
108 - disabled:'=ngDisabled' 108 + disabled:'=ngDisabled',
  109 + useAliasEntityTypes: "=?"
109 } 110 }
110 }; 111 };
111 } 112 }
@@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
20 the-form="theForm" 20 the-form="theForm"
21 ng-disabled="disabled" 21 ng-disabled="disabled"
22 tb-required="tbRequired" 22 tb-required="tbRequired"
  23 + use-alias-entity-types="useAliasEntityTypes"
23 ng-model="model.entityType"> 24 ng-model="model.entityType">
24 </tb-entity-type-select> 25 </tb-entity-type-select>
25 <tb-entity-autocomplete flex ng-if="model.entityType" 26 <tb-entity-autocomplete flex ng-if="model.entityType"
@@ -39,7 +39,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity @@ -39,7 +39,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity
39 39
40 scope.ngModelCtrl = ngModelCtrl; 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 scope.typeName = function(type) { 44 scope.typeName = function(type) {
45 return type ? types.entityTypeTranslations[type].type : ''; 45 return type ? types.entityTypeTranslations[type].type : '';
@@ -79,7 +79,8 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity @@ -79,7 +79,8 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity
79 theForm: '=?', 79 theForm: '=?',
80 tbRequired: '=?', 80 tbRequired: '=?',
81 disabled:'=ngDisabled', 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,7 +540,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
540 function success(dashboard) { 540 function success(dashboard) {
541 var name = dashboard.title; 541 var name = dashboard.title;
542 name = name.toLowerCase().replace(/\W/g,"_"); 542 name = name.toLowerCase().replace(/\W/g,"_");
543 - exportToPc(prepareExport(dashboard), name + '.json'); 543 + exportToPc(prepareDashboardExport(dashboard), name + '.json');
544 }, 544 },
545 function fail(rejection) { 545 function fail(rejection) {
546 var message = rejection; 546 var message = rejection;
@@ -552,6 +552,15 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, @@ -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 function importDashboard($event) { 564 function importDashboard($event) {
556 var deferred = $q.defer(); 565 var deferred = $q.defer();
557 openImportDialog($event, 'dashboard.import', 'dashboard.dashboard-file').then( 566 openImportDialog($event, 'dashboard.import', 'dashboard.dashboard-file').then(
@@ -383,7 +383,10 @@ export default angular.module('thingsboard.locale', []) @@ -383,7 +383,10 @@ export default angular.module('thingsboard.locale', [])
383 "idCopiedMessage": "Customer Id has been copied to clipboard", 383 "idCopiedMessage": "Customer Id has been copied to clipboard",
384 "select-customer": "Select customer", 384 "select-customer": "Select customer",
385 "no-customers-matching": "No customers matching '{{entity}}' were found.", 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 "datetime": { 391 "datetime": {
389 "date-from": "Date from", 392 "date-from": "Date from",
@@ -404,6 +407,12 @@ export default angular.module('thingsboard.locale', []) @@ -404,6 +407,12 @@ export default angular.module('thingsboard.locale', [])
404 "unassign-from-customer": "Unassign from customer", 407 "unassign-from-customer": "Unassign from customer",
405 "make-public": "Make dashboard public", 408 "make-public": "Make dashboard public",
406 "make-private": "Make dashboard private", 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 "no-dashboards-text": "No dashboards found", 416 "no-dashboards-text": "No dashboards found",
408 "no-widgets": "No widgets configured", 417 "no-widgets": "No widgets configured",
409 "add-widget": "Add new widget", 418 "add-widget": "Add new widget",
@@ -418,7 +427,8 @@ export default angular.module('thingsboard.locale', []) @@ -418,7 +427,8 @@ export default angular.module('thingsboard.locale', [])
418 "add-dashboard-text": "Add new dashboard", 427 "add-dashboard-text": "Add new dashboard",
419 "assign-dashboards": "Assign dashboards", 428 "assign-dashboards": "Assign dashboards",
420 "assign-new-dashboard": "Assign new dashboard", 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 "delete-dashboards": "Delete dashboards", 432 "delete-dashboards": "Delete dashboards",
423 "unassign-dashboards": "Unassign dashboards", 433 "unassign-dashboards": "Unassign dashboards",
424 "unassign-dashboards-action-title": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customer", 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,6 +510,7 @@ export default angular.module('thingsboard.locale', [])
500 "Please contact your administrator in order to resolve this issue.", 510 "Please contact your administrator in order to resolve this issue.",
501 "select-devices": "Select devices", 511 "select-devices": "Select devices",
502 "assignedToCustomer": "Assigned to customer", 512 "assignedToCustomer": "Assigned to customer",
  513 + "assignedToCustomers": "Assigned to customers",
503 "public": "Public", 514 "public": "Public",
504 "public-link": "Public link", 515 "public-link": "Public link",
505 "copy-public-link": "Copy public link", 516 "copy-public-link": "Copy public link",
@@ -735,6 +746,7 @@ export default angular.module('thingsboard.locale', []) @@ -735,6 +746,7 @@ export default angular.module('thingsboard.locale', [])
735 "type-alarms": "Alarms", 746 "type-alarms": "Alarms",
736 "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }", 747 "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
737 "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'", 748 "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
  749 + "type-current-customer": "Current Customer",
738 "search": "Search entities", 750 "search": "Search entities",
739 "selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected", 751 "selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
740 "entity-name": "Entity name", 752 "entity-name": "Entity name",