Browse Source

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

Feature/customer dashboards
pull/625/head
Igor Kulikov 9 years ago
committed by GitHub
parent
commit
ca6ea7dd6d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      application/src/main/data/upgrade/1.4.0/schema_update.cql
  2. 10
      application/src/main/data/upgrade/1.4.0/schema_update.sql
  3. 13
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  4. 227
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  5. 34
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
  6. 100
      application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java
  7. 6
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  8. 34
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  9. 27
      application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java
  10. 171
      application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java
  11. 118
      application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java
  12. 5
      common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
  13. 2
      common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java
  14. 78
      common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
  15. 54
      common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java
  16. 3
      common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java
  17. 6
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  18. 32
      dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java
  19. 4
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java
  20. 12
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  21. 174
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  22. 4
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  23. 61
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java
  24. 57
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java
  25. 40
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java
  26. 40
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java
  27. 50
      dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java
  28. 3
      dao/src/main/java/org/thingsboard/server/dao/service/Validator.java
  29. 8
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java
  30. 36
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java
  31. 17
      dao/src/main/resources/cassandra/schema.cql
  32. 2
      dao/src/main/resources/sql/schema.sql
  33. 104
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java
  34. 38
      dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java
  35. 150
      ui/src/app/api/dashboard.service.js
  36. 43
      ui/src/app/api/entity.service.js
  37. 4
      ui/src/app/api/user.service.js
  38. 7
      ui/src/app/common/types.constant.js
  39. 4
      ui/src/app/components/dashboard-autocomplete.directive.js
  40. 4
      ui/src/app/components/dashboard-select.directive.js
  41. 2
      ui/src/app/dashboard/add-dashboards-to-customer.controller.js
  42. 123
      ui/src/app/dashboard/assign-to-customer.controller.js
  43. 76
      ui/src/app/dashboard/assign-to-customer.tpl.html
  44. 27
      ui/src/app/dashboard/dashboard-card.scss
  45. 4
      ui/src/app/dashboard/dashboard-card.tpl.html
  46. 27
      ui/src/app/dashboard/dashboard-fieldset.tpl.html
  47. 29
      ui/src/app/dashboard/dashboard.directive.js
  48. 226
      ui/src/app/dashboard/dashboards.controller.js
  49. 6
      ui/src/app/dashboard/dashboards.tpl.html
  50. 4
      ui/src/app/dashboard/index.js
  51. 69
      ui/src/app/dashboard/manage-assigned-customers.controller.js
  52. 51
      ui/src/app/dashboard/manage-assigned-customers.tpl.html
  53. 37
      ui/src/app/entity/entity-autocomplete.directive.js
  54. 8
      ui/src/app/entity/entity-filter.tpl.html
  55. 3
      ui/src/app/entity/entity-select.directive.js
  56. 1
      ui/src/app/entity/entity-select.tpl.html
  57. 5
      ui/src/app/entity/entity-type-select.directive.js
  58. 11
      ui/src/app/import-export/import-export.service.js
  59. 16
      ui/src/app/locale/locale.constant.js

23
application/src/main/data/upgrade/1.4.0/schema_update.cql

@ -87,3 +87,26 @@ CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions (
PRIMARY KEY (( tenant_id ), partition) PRIMARY KEY (( tenant_id ), partition)
) WITH CLUSTERING ORDER BY ( partition ASC ) ) WITH CLUSTERING ORDER BY ( partition ASC )
AND compaction = { 'class' : 'LeveledCompactionStrategy' }; AND compaction = { 'class' : 'LeveledCompactionStrategy' };
DROP MATERIALIZED VIEW IF EXISTS thingsboard.dashboard_by_tenant_and_search_text;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.dashboard_by_customer_and_search_text;
DROP TABLE IF EXISTS thingsboard.dashboard;
CREATE TABLE IF NOT EXISTS thingsboard.dashboard (
id timeuuid,
tenant_id timeuuid,
title text,
search_text text,
assigned_customers text,
configuration text,
PRIMARY KEY (id, tenant_id)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
SELECT *
from thingsboard.dashboard
WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( tenant_id, search_text, id )
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );

10
application/src/main/data/upgrade/1.4.0/schema_update.sql

@ -29,3 +29,13 @@ CREATE TABLE IF NOT EXISTS audit_log (
action_failure_details varchar(1000000) action_failure_details varchar(1000000)
); );
DROP TABLE IF EXISTS dashboard;
CREATE TABLE IF NOT EXISTS dashboard (
id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
configuration varchar(10000000),
assigned_customers varchar(1000000),
search_text varchar(255),
tenant_id varchar(31),
title varchar(255)
);

13
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -423,7 +423,7 @@ public abstract class BaseController {
try { try {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId); validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
Dashboard dashboard = dashboardService.findDashboardById(dashboardId); Dashboard dashboard = dashboardService.findDashboardById(dashboardId);
checkDashboard(dashboard, true); checkDashboard(dashboard);
return dashboard; return dashboard;
} catch (Exception e) { } catch (Exception e) {
throw handleException(e, false); throw handleException(e, false);
@ -434,28 +434,23 @@ public abstract class BaseController {
try { try {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId); validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId); DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId);
SecurityUser authUser = getCurrentUser(); checkDashboard(dashboardInfo);
checkDashboard(dashboardInfo, authUser.getAuthority() != Authority.SYS_ADMIN);
return dashboardInfo; return dashboardInfo;
} catch (Exception e) { } catch (Exception e) {
throw handleException(e, false); throw handleException(e, false);
} }
} }
private void checkDashboard(DashboardInfo dashboard, boolean checkCustomerId) throws ThingsboardException { private void checkDashboard(DashboardInfo dashboard) throws ThingsboardException {
checkNotNull(dashboard); checkNotNull(dashboard);
checkTenantId(dashboard.getTenantId()); checkTenantId(dashboard.getTenantId());
SecurityUser authUser = getCurrentUser(); SecurityUser authUser = getCurrentUser();
if (authUser.getAuthority() == Authority.CUSTOMER_USER) { if (authUser.getAuthority() == Authority.CUSTOMER_USER) {
if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { if (!dashboard.isAssignedToCustomer(authUser.getCustomerId())) {
throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
ThingsboardErrorCode.PERMISSION_DENIED); ThingsboardErrorCode.PERMISSION_DENIED);
} }
} }
if (checkCustomerId &&
dashboard.getCustomerId() != null && !dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
checkCustomerId(dashboard.getCustomerId());
}
} }
ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException { ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException {

227
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -18,20 +18,22 @@ package org.thingsboard.server.controller;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException; import org.thingsboard.server.exception.ThingsboardException;
import java.util.HashSet;
import java.util.Set;
@RestController @RestController
@RequestMapping("/api") @RequestMapping("/api")
public class DashboardController extends BaseController { public class DashboardController extends BaseController {
@ -80,7 +82,7 @@ public class DashboardController extends BaseController {
Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard)); Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard));
logEntityAction(savedDashboard.getId(), savedDashboard, logEntityAction(savedDashboard.getId(), savedDashboard,
savedDashboard.getCustomerId(), null,
dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedDashboard; return savedDashboard;
@ -103,7 +105,7 @@ public class DashboardController extends BaseController {
dashboardService.deleteDashboard(dashboardId); dashboardService.deleteDashboard(dashboardId);
logEntityAction(dashboardId, dashboard, logEntityAction(dashboardId, dashboard,
dashboard.getCustomerId(), null,
ActionType.DELETED, null, strDashboardId); ActionType.DELETED, null, strDashboardId);
} catch (Exception e) { } catch (Exception e) {
@ -134,7 +136,7 @@ public class DashboardController extends BaseController {
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId)); Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
logEntityAction(dashboardId, savedDashboard, logEntityAction(dashboardId, savedDashboard,
savedDashboard.getCustomerId(), customerId,
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName()); ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName());
@ -150,23 +152,22 @@ public class DashboardController extends BaseController {
} }
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/dashboard/{dashboardId}", method = RequestMethod.DELETE) @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public Dashboard unassignDashboardFromCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { public Dashboard unassignDashboardFromCustomer(@PathVariable("customerId") String strCustomerId,
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter("customerId", strCustomerId);
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
Customer customer = checkCustomerId(customerId);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId); Dashboard dashboard = checkDashboardId(dashboardId);
if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
throw new IncorrectParameterException("Dashboard isn't assigned to any customer!");
}
Customer customer = checkCustomerId(dashboard.getCustomerId()); Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId));
logEntityAction(dashboardId, dashboard, logEntityAction(dashboardId, dashboard,
dashboard.getCustomerId(), customerId,
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName()); ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName());
return savedDashboard; return savedDashboard;
@ -180,6 +181,158 @@ public class DashboardController extends BaseController {
} }
} }
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers", method = RequestMethod.POST)
@ResponseBody
public Dashboard updateDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
@RequestBody String[] strCustomerIds) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId);
try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId);
Set<CustomerId> customerIds = new HashSet<>();
if (strCustomerIds != null) {
for (String strCustomerId : strCustomerIds) {
customerIds.add(new CustomerId(toUUID(strCustomerId)));
}
}
Set<CustomerId> addedCustomerIds = new HashSet<>();
Set<CustomerId> removedCustomerIds = new HashSet<>();
for (CustomerId customerId : customerIds) {
if (!dashboard.isAssignedToCustomer(customerId)) {
addedCustomerIds.add(customerId);
}
}
Set<ShortCustomerInfo> assignedCustomers = dashboard.getAssignedCustomers();
if (assignedCustomers != null) {
for (ShortCustomerInfo customerInfo : assignedCustomers) {
if (!customerIds.contains(customerInfo.getCustomerId())) {
removedCustomerIds.add(customerInfo.getCustomerId());
}
}
}
if (addedCustomerIds.isEmpty() && removedCustomerIds.isEmpty()) {
return dashboard;
} else {
Dashboard savedDashboard = null;
for (CustomerId customerId : addedCustomerIds) {
savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId);
logEntityAction(dashboardId, savedDashboard,
customerId,
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
}
for (CustomerId customerId : removedCustomerIds) {
ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
logEntityAction(dashboardId, dashboard,
customerId,
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
}
return savedDashboard;
}
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers/add", method = RequestMethod.POST)
@ResponseBody
public Dashboard addDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
@RequestBody String[] strCustomerIds) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId);
try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId);
Set<CustomerId> customerIds = new HashSet<>();
if (strCustomerIds != null) {
for (String strCustomerId : strCustomerIds) {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
if (!dashboard.isAssignedToCustomer(customerId)) {
customerIds.add(customerId);
}
}
}
if (customerIds.isEmpty()) {
return dashboard;
} else {
Dashboard savedDashboard = null;
for (CustomerId customerId : customerIds) {
savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId);
logEntityAction(dashboardId, savedDashboard,
customerId,
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
}
return savedDashboard;
}
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers/remove", method = RequestMethod.POST)
@ResponseBody
public Dashboard removeDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
@RequestBody String[] strCustomerIds) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId);
try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId);
Set<CustomerId> customerIds = new HashSet<>();
if (strCustomerIds != null) {
for (String strCustomerId : strCustomerIds) {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
if (dashboard.isAssignedToCustomer(customerId)) {
customerIds.add(customerId);
}
}
}
if (customerIds.isEmpty()) {
return dashboard;
} else {
Dashboard savedDashboard = null;
for (CustomerId customerId : customerIds) {
ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
logEntityAction(dashboardId, dashboard,
customerId,
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
}
return savedDashboard;
}
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -192,7 +345,7 @@ public class DashboardController extends BaseController {
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId())); Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId()));
logEntityAction(dashboardId, savedDashboard, logEntityAction(dashboardId, savedDashboard,
savedDashboard.getCustomerId(), publicCustomer.getId(),
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName()); ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
return savedDashboard; return savedDashboard;
@ -206,6 +359,33 @@ public class DashboardController extends BaseController {
} }
} }
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody
public Dashboard unassignDashboardFromPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId);
try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId);
Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId());
Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, publicCustomer.getId()));
logEntityAction(dashboardId, dashboard,
publicCustomer.getId(),
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
return savedDashboard;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -245,19 +425,20 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<DashboardInfo> getCustomerDashboards( public TimePageData<DashboardInfo> getCustomerDashboards(
@PathVariable("customerId") String strCustomerId, @PathVariable("customerId") String strCustomerId,
@RequestParam int limit, @RequestParam int limit,
@RequestParam(required = false) String textSearch, @RequestParam(required = false) Long startTime,
@RequestParam(required = false) String idOffset, @RequestParam(required = false) Long endTime,
@RequestParam(required = false) String textOffset) throws ThingsboardException { @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
@RequestParam(required = false) String offset) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter("customerId", strCustomerId);
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId); checkCustomerId(customerId);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get());
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

34
application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java

@ -24,6 +24,7 @@ import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.cassandra.CassandraCluster; import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.cassandra.CassandraInstallCluster; import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.util.NoSqlDao; import org.thingsboard.server.dao.util.NoSqlDao;
import org.thingsboard.server.service.install.cql.CQLStatementsParser; import org.thingsboard.server.service.install.cql.CQLStatementsParser;
import org.thingsboard.server.service.install.cql.CassandraDbHelper; import org.thingsboard.server.service.install.cql.CassandraDbHelper;
@ -33,6 +34,8 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.List; import java.util.List;
import static org.thingsboard.server.service.install.DatabaseHelper.*;
@Service @Service
@NoSqlDao @NoSqlDao
@Profile("install") @Profile("install")
@ -40,12 +43,6 @@ import java.util.List;
public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
private static final String SCHEMA_UPDATE_CQL = "schema_update.cql"; private static final String SCHEMA_UPDATE_CQL = "schema_update.cql";
public static final String DEVICE = "device";
public static final String TENANT_ID = "tenant_id";
public static final String CUSTOMER_ID = "customer_id";
public static final String SEARCH_TEXT = "search_text";
public static final String ADDITIONAL_INFO = "additional_info";
public static final String ASSET = "asset";
@Value("${install.data_dir}") @Value("${install.data_dir}")
private String dataDir; private String dataDir;
@ -56,6 +53,9 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
@Autowired @Autowired
private CassandraInstallCluster installCluster; private CassandraInstallCluster installCluster;
@Autowired
private DashboardService dashboardService;
@Override @Override
public void upgradeDatabase(String fromVersion) throws Exception { public void upgradeDatabase(String fromVersion) throws Exception {
@ -160,10 +160,32 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
case "1.3.0": case "1.3.0":
break; break;
case "1.3.1": case "1.3.1":
cluster.getSession();
ks = cluster.getCluster().getMetadata().getKeyspace(cluster.getKeyspaceName());
log.info("Dumping dashboards ...");
Path dashboardsDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), DASHBOARD,
new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION},
new String[]{"", "", "", "", "", "", ""},
"tb-dashboards", true);
log.info("Dashboards dumped.");
log.info("Updating schema ..."); log.info("Updating schema ...");
schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL); schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile); loadCql(schemaUpdateFile);
log.info("Schema updated."); log.info("Schema updated.");
log.info("Restoring dashboards ...");
if (dashboardsDump != null) {
CassandraDbHelper.loadCf(ks, cluster.getSession(), DASHBOARD,
new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump, true);
DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, false);
Files.deleteIfExists(dashboardsDump);
}
log.info("Dashboards restored.");
break; break;
default: default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);

100
application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java

@ -0,0 +1,100 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.dao.dashboard.DashboardService;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
/**
* Created by igor on 2/27/18.
*/
@Slf4j
public class DatabaseHelper {
public static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
public static final String DEVICE = "device";
public static final String TENANT_ID = "tenant_id";
public static final String CUSTOMER_ID = "customer_id";
public static final String SEARCH_TEXT = "search_text";
public static final String ADDITIONAL_INFO = "additional_info";
public static final String ASSET = "asset";
public static final String DASHBOARD = "dashboard";
public static final String ID = "id";
public static final String TITLE = "title";
public static final String ASSIGNED_CUSTOMERS = "assigned_customers";
public static final String CONFIGURATION = "configuration";
public static final ObjectMapper objectMapper = new ObjectMapper();
public static void upgradeTo40_assignDashboards(Path dashboardsDump, DashboardService dashboardService, boolean sql) throws Exception {
JavaType assignedCustomersType =
objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(dashboardsDump), CSV_DUMP_FORMAT.withFirstRecordAsHeader())) {
csvParser.forEach(record -> {
String customerIdString = record.get(CUSTOMER_ID);
String assignedCustomersString = record.get(ASSIGNED_CUSTOMERS);
DashboardId dashboardId = new DashboardId(toUUID(record.get(ID), sql));
List<CustomerId> customerIds = new ArrayList<>();
if (!StringUtils.isEmpty(assignedCustomersString)) {
try {
Set<ShortCustomerInfo> assignedCustomers = objectMapper.readValue(assignedCustomersString, assignedCustomersType);
assignedCustomers.forEach((customerInfo) -> {
CustomerId customerId = customerInfo.getCustomerId();
if (!customerId.isNullUid()) {
customerIds.add(customerId);
}
});
} catch (IOException e) {
log.error("Unable to parse assigned customers field", e);
}
}
if (!StringUtils.isEmpty(customerIdString)) {
CustomerId customerId = new CustomerId(toUUID(customerIdString, sql));
if (!customerId.isNullUid()) {
customerIds.add(customerId);
}
}
for (CustomerId customerId : customerIds) {
dashboardService.assignDashboardToCustomer(dashboardId, customerId);
}
});
}
}
private static UUID toUUID(String src, boolean sql) {
if (sql) {
return UUIDConverter.fromString(src);
} else {
return UUID.fromString(src);
}
}
}

6
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -339,8 +339,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
JsonNode dashboardJson = objectMapper.readTree(path.toFile()); JsonNode dashboardJson = objectMapper.readTree(path.toFile());
Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class); Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
dashboard.setTenantId(tenantId); dashboard.setTenantId(tenantId);
dashboard.setCustomerId(customerId); Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
dashboardService.saveDashboard(dashboard); if (customerId != null && !customerId.isNullUid()) {
dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId);
}
} catch (Exception e) { } catch (Exception e) {
log.error("Unable to load dashboard from json: [{}]", path.toString()); log.error("Unable to load dashboard from json: [{}]", path.toString());
throw new RuntimeException("Unable to load dashboard from json", e); throw new RuntimeException("Unable to load dashboard from json", e);

34
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -17,18 +17,26 @@
package org.thingsboard.server.service.install; package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
import org.thingsboard.server.service.install.cql.CassandraDbHelper;
import org.thingsboard.server.service.install.sql.SqlDbHelper;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager; import java.sql.DriverManager;
import static org.thingsboard.server.service.install.DatabaseHelper.*;
import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION;
@Service @Service
@Profile("install") @Profile("install")
@Slf4j @Slf4j
@ -49,6 +57,9 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
@Value("${spring.datasource.password}") @Value("${spring.datasource.password}")
private String dbPassword; private String dbPassword;
@Autowired
private DashboardService dashboardService;
@Override @Override
public void upgradeDatabase(String fromVersion) throws Exception { public void upgradeDatabase(String fromVersion) throws Exception {
switch (fromVersion) { switch (fromVersion) {
@ -62,13 +73,30 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Schema updated."); log.info("Schema updated.");
break; break;
case "1.3.1": case "1.3.1":
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Dumping dashboards ...");
Path dashboardsDump = SqlDbHelper.dumpTableIfExists(conn, DASHBOARD,
new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION},
new String[]{"", "", "", "", "", "", ""},
"tb-dashboards", true);
log.info("Dashboards dumped.");
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8")); String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
log.info("Schema updated.");
log.info("Restoring dashboards ...");
if (dashboardsDump != null) {
SqlDbHelper.loadTable(conn, DASHBOARD,
new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump, true);
DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, true);
Files.deleteIfExists(dashboardsDump);
}
log.info("Dashboards restored.");
} }
log.info("Schema updated.");
break; break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);

27
application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java

@ -28,16 +28,25 @@ import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.*; import java.util.*;
public class CassandraDbHelper { import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT;
private static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N"); public class CassandraDbHelper {
public static Path dumpCfIfExists(KeyspaceMetadata ks, Session session, String cfName, public static Path dumpCfIfExists(KeyspaceMetadata ks, Session session, String cfName,
String[] columns, String[] defaultValues, String dumpPrefix) throws Exception { String[] columns, String[] defaultValues, String dumpPrefix) throws Exception {
return dumpCfIfExists(ks, session, cfName, columns, defaultValues, dumpPrefix, false);
}
public static Path dumpCfIfExists(KeyspaceMetadata ks, Session session, String cfName,
String[] columns, String[] defaultValues, String dumpPrefix, boolean printHeader) throws Exception {
if (ks.getTable(cfName) != null) { if (ks.getTable(cfName) != null) {
Path dumpFile = Files.createTempFile(dumpPrefix, null); Path dumpFile = Files.createTempFile(dumpPrefix, null);
Files.deleteIfExists(dumpFile); Files.deleteIfExists(dumpFile);
try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), CSV_DUMP_FORMAT)) { CSVFormat csvFormat = CSV_DUMP_FORMAT;
if (printHeader) {
csvFormat = csvFormat.withHeader(columns);
}
try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), csvFormat)) {
Statement stmt = new SimpleStatement("SELECT * FROM " + cfName); Statement stmt = new SimpleStatement("SELECT * FROM " + cfName);
stmt.setFetchSize(1000); stmt.setFetchSize(1000);
ResultSet rs = session.execute(stmt); ResultSet rs = session.execute(stmt);
@ -75,9 +84,19 @@ public class CassandraDbHelper {
} }
public static void loadCf(KeyspaceMetadata ks, Session session, String cfName, String[] columns, Path sourceFile) throws Exception { public static void loadCf(KeyspaceMetadata ks, Session session, String cfName, String[] columns, Path sourceFile) throws Exception {
loadCf(ks, session, cfName, columns, sourceFile, false);
}
public static void loadCf(KeyspaceMetadata ks, Session session, String cfName, String[] columns, Path sourceFile, boolean parseHeader) throws Exception {
TableMetadata tableMetadata = ks.getTable(cfName); TableMetadata tableMetadata = ks.getTable(cfName);
PreparedStatement prepared = session.prepare(createInsertStatement(cfName, columns)); PreparedStatement prepared = session.prepare(createInsertStatement(cfName, columns));
try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), CSV_DUMP_FORMAT.withHeader(columns))) { CSVFormat csvFormat = CSV_DUMP_FORMAT;
if (parseHeader) {
csvFormat = csvFormat.withFirstRecordAsHeader();
} else {
csvFormat = CSV_DUMP_FORMAT.withHeader(columns);
}
try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), csvFormat)) {
csvParser.forEach(record -> { csvParser.forEach(record -> {
BoundStatement boundStatement = prepared.bind(); BoundStatement boundStatement = prepared.bind();
for (String column : columns) { for (String column : columns) {

171
application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java

@ -0,0 +1,171 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install.sql;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVRecord;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT;
/**
* Created by igor on 2/27/18.
*/
@Slf4j
public class SqlDbHelper {
public static Path dumpTableIfExists(Connection conn, String tableName,
String[] columns, String[] defaultValues, String dumpPrefix) throws Exception {
return dumpTableIfExists(conn, tableName, columns, defaultValues, dumpPrefix, false);
}
public static Path dumpTableIfExists(Connection conn, String tableName,
String[] columns, String[] defaultValues, String dumpPrefix, boolean printHeader) throws Exception {
if (tableExists(conn, tableName)) {
Path dumpFile = Files.createTempFile(dumpPrefix, null);
Files.deleteIfExists(dumpFile);
CSVFormat csvFormat = CSV_DUMP_FORMAT;
if (printHeader) {
csvFormat = csvFormat.withHeader(columns);
}
try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), csvFormat)) {
try (PreparedStatement stmt = conn.prepareStatement("SELECT * FROM " + tableName)) {
try (ResultSet tableRes = stmt.executeQuery()) {
ResultSetMetaData resMetaData = tableRes.getMetaData();
Map<String, Integer> columnIndexMap = new HashMap<>();
for (int i = 1; i <= resMetaData.getColumnCount(); i++) {
String columnName = resMetaData.getColumnName(i);
columnIndexMap.put(columnName.toUpperCase(), i);
}
while(tableRes.next()) {
dumpRow(tableRes, columnIndexMap, columns, defaultValues, csvPrinter);
}
}
}
}
return dumpFile;
} else {
return null;
}
}
private static boolean tableExists(Connection conn, String tableName) {
try (Statement stmt = conn.createStatement()) {
stmt.executeQuery("select * from " + tableName + " where 1=0");
return true;
} catch (Exception e) {
return false;
}
}
public static void loadTable(Connection conn, String tableName, String[] columns, Path sourceFile) throws Exception {
loadTable(conn, tableName, columns, sourceFile, false);
}
public static void loadTable(Connection conn, String tableName, String[] columns, Path sourceFile, boolean parseHeader) throws Exception {
CSVFormat csvFormat = CSV_DUMP_FORMAT;
if (parseHeader) {
csvFormat = csvFormat.withFirstRecordAsHeader();
} else {
csvFormat = CSV_DUMP_FORMAT.withHeader(columns);
}
try (PreparedStatement prepared = conn.prepareStatement(createInsertStatement(tableName, columns))) {
try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), csvFormat)) {
csvParser.forEach(record -> {
try {
for (int i = 0; i < columns.length; i++) {
setColumnValue(i, columns[i], record, prepared);
}
prepared.execute();
} catch (SQLException e) {
log.error("Unable to load table record!", e);
}
});
}
}
}
private static void dumpRow(ResultSet res, Map<String, Integer> columnIndexMap, String[] columns,
String[] defaultValues, CSVPrinter csvPrinter) throws Exception {
List<String> record = new ArrayList<>();
for (int i=0;i<columns.length;i++) {
String column = columns[i];
String defaultValue;
if (defaultValues != null && i < defaultValues.length) {
defaultValue = defaultValues[i];
} else {
defaultValue = "";
}
record.add(getColumnValue(column, defaultValue, columnIndexMap, res));
}
csvPrinter.printRecord(record);
}
private static String getColumnValue(String column, String defaultValue, Map<String, Integer> columnIndexMap, ResultSet res) {
int index = columnIndexMap.containsKey(column.toUpperCase()) ? columnIndexMap.get(column.toUpperCase()) : -1;
if (index > -1) {
String str;
try {
Object obj = res.getObject(index);
if (obj == null) {
return null;
} else {
str = obj.toString();
}
} catch (Exception e) {
str = "";
}
return str;
} else {
return defaultValue;
}
}
private static void setColumnValue(int index, String column,
CSVRecord record, PreparedStatement preparedStatement) throws SQLException {
String value = record.get(column);
int type = preparedStatement.getParameterMetaData().getParameterType(index + 1);
preparedStatement.setObject(index + 1, value, type);
}
private static String createInsertStatement(String tableName, String[] columns) {
StringBuilder insertStatementBuilder = new StringBuilder();
insertStatementBuilder.append("INSERT INTO ").append(tableName).append(" (");
for (String column : columns) {
insertStatementBuilder.append(column).append(",");
}
insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
insertStatementBuilder.append(") VALUES (");
for (String column : columns) {
insertStatementBuilder.append("?").append(",");
}
insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
insertStatementBuilder.append(")");
return insertStatementBuilder.toString();
}
}

118
application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java

@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.sql.Time;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -29,6 +30,8 @@ import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.junit.After; import org.junit.After;
@ -82,8 +85,6 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Assert.assertNotNull(savedDashboard.getId()); Assert.assertNotNull(savedDashboard.getId());
Assert.assertTrue(savedDashboard.getCreatedTime() > 0); Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId()); Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId());
Assert.assertNotNull(savedDashboard.getCustomerId());
Assert.assertEquals(NULL_UUID, savedDashboard.getCustomerId().getId());
Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle()); Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
savedDashboard.setTitle("My new dashboard"); savedDashboard.setTitle("My new dashboard");
@ -136,17 +137,20 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString() Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertEquals(savedCustomer.getId(), assignedDashboard.getCustomerId());
Assert.assertTrue(assignedDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo()));
Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertEquals(savedCustomer.getId(), foundDashboard.getCustomerId()); Assert.assertTrue(foundDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo()));
Dashboard unassignedDashboard = Dashboard unassignedDashboard =
doDelete("/api/customer/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); doDelete("/api/customer/"+savedCustomer.getId().getId().toString()+"/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDashboard.getCustomerId().getId());
Assert.assertTrue(unassignedDashboard.getAssignedCustomers() == null || unassignedDashboard.getAssignedCustomers().isEmpty());
foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertEquals(ModelConstants.NULL_UUID, foundDashboard.getCustomerId().getId());
Assert.assertTrue(foundDashboard.getAssignedCustomers() == null || foundDashboard.getAssignedCustomers().isEmpty());
} }
@Test @Test
@ -320,11 +324,11 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
} }
List<DashboardInfo> loadedDashboards = new ArrayList<>(); List<DashboardInfo> loadedDashboards = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(21); TimePageLink pageLink = new TimePageLink(21);
TextPageData<DashboardInfo> pageData = null; TimePageData<DashboardInfo> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", pageData = doGetTypedWithTimePageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink); new TypeReference<TimePageData<DashboardInfo>>(){}, pageLink);
loadedDashboards.addAll(pageData.getData()); loadedDashboards.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -336,93 +340,5 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Assert.assertEquals(dashboards, loadedDashboards); Assert.assertEquals(dashboards, loadedDashboards);
} }
@Test
public void testFindCustomerDashboardsByTitle() throws Exception {
Customer customer = new Customer();
customer.setTitle("Test customer");
customer = doPost("/api/customer", customer, Customer.class);
CustomerId customerId = customer.getId();
String title1 = "Dashboard title 1";
List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
for (int i=0;i<125;i++) {
Dashboard dashboard = new Dashboard();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title1+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title);
dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
dashboardsTitle1.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
}
String title2 = "Dashboard title 2";
List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
for (int i=0;i<143;i++) {
Dashboard dashboard = new Dashboard();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title2+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title);
dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
dashboardsTitle2.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
}
List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(18, title1);
TextPageData<DashboardInfo> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboardsTitle1.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Collections.sort(dashboardsTitle1, idComparator);
Collections.sort(loadedDashboardsTitle1, idComparator);
Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
pageLink = new TextPageLink(7, title2);
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboardsTitle2.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Collections.sort(dashboardsTitle2, idComparator);
Collections.sort(loadedDashboardsTitle2, idComparator);
Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
for (DashboardInfo dashboard : loadedDashboardsTitle1) {
doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new TextPageLink(5, title1);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
for (DashboardInfo dashboard : loadedDashboardsTitle2) {
doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new TextPageLink(9, title2);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
} }

5
common/data/src/main/java/org/thingsboard/server/common/data/Customer.java

@ -69,6 +69,11 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
return false; return false;
} }
@JsonIgnore
public ShortCustomerInfo toShortCustomerInfo() {
return new ShortCustomerInfo(id, title, isPublic());
}
@Override @Override
@JsonProperty(access = Access.READ_ONLY) @JsonProperty(access = Access.READ_ONLY)
public String getName() { public String getName() {

2
common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java

@ -79,8 +79,6 @@ public class Dashboard extends DashboardInfo {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("Dashboard [tenantId="); builder.append("Dashboard [tenantId=");
builder.append(getTenantId()); builder.append(getTenantId());
builder.append(", customerId=");
builder.append(getCustomerId());
builder.append(", title="); builder.append(", title=");
builder.append(getTitle()); builder.append(getTitle());
builder.append(", configuration="); builder.append(", configuration=");

78
common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java

@ -20,11 +20,13 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import java.util.*;
public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName { public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName {
private TenantId tenantId; private TenantId tenantId;
private CustomerId customerId;
private String title; private String title;
private Set<ShortCustomerInfo> assignedCustomers;
public DashboardInfo() { public DashboardInfo() {
super(); super();
@ -37,8 +39,8 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
public DashboardInfo(DashboardInfo dashboardInfo) { public DashboardInfo(DashboardInfo dashboardInfo) {
super(dashboardInfo); super(dashboardInfo);
this.tenantId = dashboardInfo.getTenantId(); this.tenantId = dashboardInfo.getTenantId();
this.customerId = dashboardInfo.getCustomerId();
this.title = dashboardInfo.getTitle(); this.title = dashboardInfo.getTitle();
this.assignedCustomers = dashboardInfo.getAssignedCustomers();
} }
public TenantId getTenantId() { public TenantId getTenantId() {
@ -49,14 +51,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.tenantId = tenantId; this.tenantId = tenantId;
} }
public CustomerId getCustomerId() {
return customerId;
}
public void setCustomerId(CustomerId customerId) {
this.customerId = customerId;
}
public String getTitle() { public String getTitle() {
return title; return title;
} }
@ -65,6 +59,62 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.title = title; this.title = title;
} }
public Set<ShortCustomerInfo> getAssignedCustomers() {
return assignedCustomers;
}
public void setAssignedCustomers(Set<ShortCustomerInfo> assignedCustomers) {
this.assignedCustomers = assignedCustomers;
}
public boolean isAssignedToCustomer(CustomerId customerId) {
return this.assignedCustomers != null && this.assignedCustomers.contains(new ShortCustomerInfo(customerId, null, false));
}
public ShortCustomerInfo getAssignedCustomerInfo(CustomerId customerId) {
if (this.assignedCustomers != null) {
for (ShortCustomerInfo customerInfo : this.assignedCustomers) {
if (customerInfo.getCustomerId().equals(customerId)) {
return customerInfo;
}
}
}
return null;
}
public boolean addAssignedCustomer(Customer customer) {
ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
return false;
} else {
if (this.assignedCustomers == null) {
this.assignedCustomers = new HashSet<>();
}
this.assignedCustomers.add(customerInfo);
return true;
}
}
public boolean updateAssignedCustomer(Customer customer) {
ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
this.assignedCustomers.add(customerInfo);
return true;
} else {
return false;
}
}
public boolean removeAssignedCustomer(Customer customer) {
ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
this.assignedCustomers.remove(customerInfo);
return true;
} else {
return false;
}
}
@Override @Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY) @JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() { public String getName() {
@ -80,7 +130,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
int result = super.hashCode(); int result = super.hashCode();
result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
result = prime * result + ((title == null) ? 0 : title.hashCode()); result = prime * result + ((title == null) ? 0 : title.hashCode());
return result; return result;
@ -95,11 +144,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
if (getClass() != obj.getClass()) if (getClass() != obj.getClass())
return false; return false;
DashboardInfo other = (DashboardInfo) obj; DashboardInfo other = (DashboardInfo) obj;
if (customerId == null) {
if (other.customerId != null)
return false;
} else if (!customerId.equals(other.customerId))
return false;
if (tenantId == null) { if (tenantId == null) {
if (other.tenantId != null) if (other.tenantId != null)
return false; return false;
@ -118,8 +162,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("DashboardInfo [tenantId="); builder.append("DashboardInfo [tenantId=");
builder.append(tenantId); builder.append(tenantId);
builder.append(", customerId=");
builder.append(customerId);
builder.append(", title="); builder.append(", title=");
builder.append(title); builder.append(title);
builder.append("]"); builder.append("]");

54
common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java

@ -0,0 +1,54 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.id.CustomerId;
/**
* Created by igor on 2/27/18.
*/
@AllArgsConstructor
public class ShortCustomerInfo {
@Getter @Setter
private CustomerId customerId;
@Getter @Setter
private String title;
@Getter @Setter
private boolean isPublic;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ShortCustomerInfo that = (ShortCustomerInfo) o;
return customerId.equals(that.customerId);
}
@Override
public int hashCode() {
return customerId.hashCode();
}
}

3
common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.relation;
public enum RelationTypeGroup { public enum RelationTypeGroup {
COMMON, COMMON,
ALARM ALARM,
DASHBOARD
} }

6
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java

@ -97,7 +97,9 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
public Customer saveCustomer(Customer customer) { public Customer saveCustomer(Customer customer) {
log.trace("Executing saveCustomer [{}]", customer); log.trace("Executing saveCustomer [{}]", customer);
customerValidator.validate(customer); customerValidator.validate(customer);
return customerDao.save(customer); Customer savedCustomer = customerDao.save(customer);
dashboardService.updateCustomerDashboards(savedCustomer.getId());
return savedCustomer;
} }
@Override @Override
@ -108,7 +110,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
if (customer == null) { if (customer == null) {
throw new IncorrectParameterException("Unable to delete non-existent customer."); throw new IncorrectParameterException("Unable to delete non-existent customer.");
} }
dashboardService.unassignCustomerDashboards(customer.getTenantId(), customerId); dashboardService.unassignCustomerDashboards(customerId);
assetService.unassignCustomerAssets(customer.getTenantId(), customerId); assetService.unassignCustomerAssets(customer.getTenantId(), customerId);
deviceService.unassignCustomerDevices(customer.getTenantId(), customerId); deviceService.unassignCustomerDevices(customer.getTenantId(), customerId);
userService.deleteCustomerUsers(customer.getTenantId(), customerId); userService.deleteCustomerUsers(customer.getTenantId(), customerId);

32
dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java

@ -15,16 +15,26 @@
*/ */
package org.thingsboard.server.dao.dashboard; package org.thingsboard.server.dao.dashboard;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.nosql.DashboardInfoEntity; import org.thingsboard.server.dao.model.nosql.DashboardInfoEntity;
import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.util.NoSqlDao; import org.thingsboard.server.dao.util.NoSqlDao;
import java.util.Arrays; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -37,6 +47,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@NoSqlDao @NoSqlDao
public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao { public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
@Autowired
private RelationDao relationDao;
@Override @Override
protected Class<DashboardInfoEntity> getColumnFamilyClass() { protected Class<DashboardInfoEntity> getColumnFamilyClass() {
return DashboardInfoEntity.class; return DashboardInfoEntity.class;
@ -59,15 +72,18 @@ public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<Da
} }
@Override @Override
public List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) { public ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) {
log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink); log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
List<DashboardInfoEntity> dashboardEntities = findPageWithTextSearch(DASHBOARD_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
Arrays.asList(eq(DASHBOARD_CUSTOMER_ID_PROPERTY, customerId),
eq(DASHBOARD_TENANT_ID_PROPERTY, tenantId)),
pageLink);
log.trace("Found dashboards [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", dashboardEntities, tenantId, customerId, pageLink); ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink);
return DaoUtil.convertDataList(dashboardEntities);
return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<DashboardInfo>>) input -> {
List<ListenableFuture<DashboardInfo>> dashboardFutures = new ArrayList<>(input.size());
for (EntityRelation relation : input) {
dashboardFutures.add(findByIdAsync(relation.getTo().getId()));
}
return Futures.successfulAsList(dashboardFutures);
});
} }
} }

4
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java

@ -15,8 +15,10 @@
*/ */
package org.thingsboard.server.dao.dashboard; package org.thingsboard.server.dao.dashboard;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import java.util.List; import java.util.List;
@ -44,6 +46,6 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> {
* @param pageLink the page link * @param pageLink the page link
* @return the list of dashboard objects * @return the list of dashboard objects
*/ */
List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink); ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink);
} }

12
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java

@ -23,6 +23,10 @@ import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import java.util.Set;
public interface DashboardService { public interface DashboardService {
@ -38,7 +42,7 @@ public interface DashboardService {
Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId); Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId);
Dashboard unassignDashboardFromCustomer(DashboardId dashboardId); Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId);
void deleteDashboard(DashboardId dashboardId); void deleteDashboard(DashboardId dashboardId);
@ -46,8 +50,10 @@ public interface DashboardService {
void deleteDashboardsByTenantId(TenantId tenantId); void deleteDashboardsByTenantId(TenantId tenantId);
TextPageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink);
void unassignCustomerDashboards(CustomerId customerId);
void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId); void updateCustomerDashboards(CustomerId customerId);
} }

174
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -15,30 +15,42 @@
*/ */
package org.thingsboard.server.dao.dashboard; package org.thingsboard.server.dao.dashboard;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.TimePaginatedRemover;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import java.sql.Time;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateId;
@ -59,7 +71,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Autowired @Autowired
private CustomerDao customerDao; private CustomerDao customerDao;
@Override @Override
public Dashboard findDashboardById(DashboardId dashboardId) { public Dashboard findDashboardById(DashboardId dashboardId) {
log.trace("Executing findDashboardById [{}]", dashboardId); log.trace("Executing findDashboardById [{}]", dashboardId);
@ -98,15 +110,63 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Override @Override
public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId) { public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId) {
Dashboard dashboard = findDashboardById(dashboardId); Dashboard dashboard = findDashboardById(dashboardId);
dashboard.setCustomerId(customerId); Customer customer = customerDao.findById(customerId.getId());
return saveDashboard(dashboard); if (customer == null) {
throw new DataValidationException("Can't assign dashboard to non-existent customer!");
}
if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) {
throw new DataValidationException("Can't assign dashboard to customer from different tenant!");
}
if (dashboard.addAssignedCustomer(customer)) {
try {
createRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
} catch (ExecutionException | InterruptedException e) {
log.warn("[{}] Failed to create dashboard relation. Customer Id: [{}]", dashboardId, customerId);
throw new RuntimeException(e);
}
return saveDashboard(dashboard);
} else {
return dashboard;
}
} }
@Override @Override
public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId) { public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId) {
Dashboard dashboard = findDashboardById(dashboardId); Dashboard dashboard = findDashboardById(dashboardId);
dashboard.setCustomerId(null); Customer customer = customerDao.findById(customerId.getId());
return saveDashboard(dashboard); if (customer == null) {
throw new DataValidationException("Can't unassign dashboard from non-existent customer!");
}
if (dashboard.removeAssignedCustomer(customer)) {
try {
deleteRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
} catch (ExecutionException | InterruptedException e) {
log.warn("[{}] Failed to delete dashboard relation. Customer Id: [{}]", dashboardId, customerId);
throw new RuntimeException(e);
}
return saveDashboard(dashboard);
} else {
return dashboard;
}
}
private Dashboard updateAssignedCustomer(DashboardId dashboardId, Customer customer) {
Dashboard dashboard = findDashboardById(dashboardId);
if (dashboard.updateAssignedCustomer(customer)) {
return saveDashboard(dashboard);
} else {
return dashboard;
}
}
private void deleteRelation(EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
log.debug("Deleting Dashboard relation: {}", dashboardRelation);
relationService.deleteRelationAsync(dashboardRelation).get();
}
private void createRelation(EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
log.debug("Creating Dashboard relation: {}", dashboardRelation);
relationService.saveRelationAsync(dashboardRelation).get();
} }
@Override @Override
@ -134,23 +194,44 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
} }
@Override @Override
public TextPageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) { public ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) {
log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink);
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
Validator.validateId(customerId, "Incorrect customerId " + customerId); Validator.validateId(customerId, "Incorrect customerId " + customerId);
Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<DashboardInfo> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); ListenableFuture<List<DashboardInfo>> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
return new TextPageData<>(dashboards, pageLink);
return Futures.transform(dashboards, new Function<List<DashboardInfo>, TimePageData<DashboardInfo>>() {
@Nullable
@Override
public TimePageData<DashboardInfo> apply(@Nullable List<DashboardInfo> dashboards) {
return new TimePageData<>(dashboards, pageLink);
}
});
} }
@Override @Override
public void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId) { public void unassignCustomerDashboards(CustomerId customerId) {
log.trace("Executing unassignCustomerDashboards, tenantId [{}], customerId [{}]", tenantId, customerId); log.trace("Executing unassignCustomerDashboards, customerId [{}]", customerId);
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
Validator.validateId(customerId, "Incorrect customerId " + customerId); Validator.validateId(customerId, "Incorrect customerId " + customerId);
new CustomerDashboardsUnassigner(tenantId).removeEntities(customerId); Customer customer = customerDao.findById(customerId.getId());
if (customer == null) {
throw new DataValidationException("Can't unassign dashboards from non-existent customer!");
}
new CustomerDashboardsUnassigner(customer).removeEntities(customer);
} }
@Override
public void updateCustomerDashboards(CustomerId customerId) {
log.trace("Executing updateCustomerDashboards, customerId [{}]", customerId);
Validator.validateId(customerId, "Incorrect customerId " + customerId);
Customer customer = customerDao.findById(customerId.getId());
if (customer == null) {
throw new DataValidationException("Can't update dashboards for non-existent customer!");
}
new CustomerDashboardsUpdater(customer).removeEntities(customer);
}
private DataValidator<Dashboard> dashboardValidator = private DataValidator<Dashboard> dashboardValidator =
new DataValidator<Dashboard>() { new DataValidator<Dashboard>() {
@Override @Override
@ -166,17 +247,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
throw new DataValidationException("Dashboard is referencing to non-existent tenant!"); throw new DataValidationException("Dashboard is referencing to non-existent tenant!");
} }
} }
if (dashboard.getCustomerId() == null) {
dashboard.setCustomerId(new CustomerId(ModelConstants.NULL_UUID));
} else if (!dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
Customer customer = customerDao.findById(dashboard.getCustomerId().getId());
if (customer == null) {
throw new DataValidationException("Can't assign dashboard to non-existent customer!");
}
if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) {
throw new DataValidationException("Can't assign dashboard to customer from different tenant!");
}
}
} }
}; };
@ -194,24 +264,54 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
} }
}; };
private class CustomerDashboardsUnassigner extends PaginatedRemover<CustomerId, DashboardInfo> { private class CustomerDashboardsUnassigner extends TimePaginatedRemover<Customer, DashboardInfo> {
private TenantId tenantId;
CustomerDashboardsUnassigner(TenantId tenantId) { private Customer customer;
this.tenantId = tenantId;
CustomerDashboardsUnassigner(Customer customer) {
this.customer = customer;
} }
@Override @Override
protected List<DashboardInfo> findEntities(CustomerId id, TextPageLink pageLink) { protected List<DashboardInfo> findEntities(Customer customer, TimePageLink pageLink) {
return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink); try {
return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get();
} catch (InterruptedException | ExecutionException e) {
log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", customer.getTenantId().getId(), customer.getId().getId());
throw new RuntimeException(e);
}
} }
@Override @Override
protected void removeEntity(DashboardInfo entity) { protected void removeEntity(DashboardInfo entity) {
unassignDashboardFromCustomer(new DashboardId(entity.getUuidId())); unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()), this.customer.getId());
} }
} }
private class CustomerDashboardsUpdater extends TimePaginatedRemover<Customer, DashboardInfo> {
private Customer customer;
CustomerDashboardsUpdater(Customer customer) {
this.customer = customer;
}
@Override
protected List<DashboardInfo> findEntities(Customer customer, TimePageLink pageLink) {
try {
return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get();
} catch (InterruptedException | ExecutionException e) {
log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", customer.getTenantId().getId(), customer.getId().getId());
throw new RuntimeException(e);
}
}
@Override
protected void removeEntity(DashboardInfo entity) {
updateAssignedCustomer(new DashboardId(entity.getUuidId()), this.customer);
}
}
} }

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

@ -266,13 +266,11 @@ public class ModelConstants {
*/ */
public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard"; public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard";
public static final String DASHBOARD_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String DASHBOARD_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String DASHBOARD_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY; public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY;
public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration"; public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration";
public static final String DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY = "assigned_customers";
public static final String DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_tenant_and_search_text"; public static final String DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_tenant_and_search_text";
public static final String DASHBOARD_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_customer_and_search_text";
/** /**
* Cassandra plugin metadata constants. * Cassandra plugin metadata constants.

61
dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java

@ -19,16 +19,23 @@ import com.datastax.driver.core.utils.UUIDs;
import com.datastax.driver.mapping.annotations.Column; import com.datastax.driver.mapping.annotations.Column;
import com.datastax.driver.mapping.annotations.PartitionKey; import com.datastax.driver.mapping.annotations.PartitionKey;
import com.datastax.driver.mapping.annotations.Table; import com.datastax.driver.mapping.annotations.Table;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.model.SearchTextEntity;
import org.thingsboard.server.dao.model.type.JsonCodec; import org.thingsboard.server.dao.model.type.JsonCodec;
import java.io.IOException;
import java.util.HashSet;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.*; import static org.thingsboard.server.dao.model.ModelConstants.*;
@ -36,8 +43,13 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@Table(name = DASHBOARD_COLUMN_FAMILY_NAME) @Table(name = DASHBOARD_COLUMN_FAMILY_NAME)
@EqualsAndHashCode @EqualsAndHashCode
@ToString @ToString
@Slf4j
public final class DashboardEntity implements SearchTextEntity<Dashboard> { public final class DashboardEntity implements SearchTextEntity<Dashboard> {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final JavaType assignedCustomersType =
objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
@PartitionKey(value = 0) @PartitionKey(value = 0)
@Column(name = ID_PROPERTY) @Column(name = ID_PROPERTY)
private UUID id; private UUID id;
@ -46,16 +58,15 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
@Column(name = DASHBOARD_TENANT_ID_PROPERTY) @Column(name = DASHBOARD_TENANT_ID_PROPERTY)
private UUID tenantId; private UUID tenantId;
@PartitionKey(value = 2)
@Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)
private UUID customerId;
@Column(name = DASHBOARD_TITLE_PROPERTY) @Column(name = DASHBOARD_TITLE_PROPERTY)
private String title; private String title;
@Column(name = SEARCH_TEXT_PROPERTY) @Column(name = SEARCH_TEXT_PROPERTY)
private String searchText; private String searchText;
@Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
private String assignedCustomers;
@Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class) @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
private JsonNode configuration; private JsonNode configuration;
@ -70,10 +81,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
if (dashboard.getTenantId() != null) { if (dashboard.getTenantId() != null) {
this.tenantId = dashboard.getTenantId().getId(); this.tenantId = dashboard.getTenantId().getId();
} }
if (dashboard.getCustomerId() != null) {
this.customerId = dashboard.getCustomerId().getId();
}
this.title = dashboard.getTitle(); this.title = dashboard.getTitle();
if (dashboard.getAssignedCustomers() != null) {
try {
this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers());
} catch (JsonProcessingException e) {
log.error("Unable to serialize assigned customers to string!", e);
}
}
this.configuration = dashboard.getConfiguration(); this.configuration = dashboard.getConfiguration();
} }
@ -93,14 +108,6 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
this.tenantId = tenantId; this.tenantId = tenantId;
} }
public UUID getCustomerId() {
return customerId;
}
public void setCustomerId(UUID customerId) {
this.customerId = customerId;
}
public String getTitle() { public String getTitle() {
return title; return title;
} }
@ -109,6 +116,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
this.title = title; this.title = title;
} }
public String getAssignedCustomers() {
return assignedCustomers;
}
public void setAssignedCustomers(String assignedCustomers) {
this.assignedCustomers = assignedCustomers;
}
public JsonNode getConfiguration() { public JsonNode getConfiguration() {
return configuration; return configuration;
} }
@ -138,10 +153,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
if (tenantId != null) { if (tenantId != null) {
dashboard.setTenantId(new TenantId(tenantId)); dashboard.setTenantId(new TenantId(tenantId));
} }
if (customerId != null) {
dashboard.setCustomerId(new CustomerId(customerId));
}
dashboard.setTitle(title); dashboard.setTitle(title);
if (!StringUtils.isEmpty(assignedCustomers)) {
try {
dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
} catch (IOException e) {
log.warn("Unable to parse assigned customers!", e);
}
}
dashboard.setConfiguration(configuration); dashboard.setConfiguration(configuration);
return dashboard; return dashboard;
} }

57
dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java

@ -19,14 +19,21 @@ import com.datastax.driver.core.utils.UUIDs;
import com.datastax.driver.mapping.annotations.Column; import com.datastax.driver.mapping.annotations.Column;
import com.datastax.driver.mapping.annotations.PartitionKey; import com.datastax.driver.mapping.annotations.PartitionKey;
import com.datastax.driver.mapping.annotations.Table; import com.datastax.driver.mapping.annotations.Table;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.model.SearchTextEntity;
import java.io.IOException;
import java.util.HashSet;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.*; import static org.thingsboard.server.dao.model.ModelConstants.*;
@ -34,8 +41,13 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@Table(name = DASHBOARD_COLUMN_FAMILY_NAME) @Table(name = DASHBOARD_COLUMN_FAMILY_NAME)
@EqualsAndHashCode @EqualsAndHashCode
@ToString @ToString
@Slf4j
public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final JavaType assignedCustomersType =
objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
@PartitionKey(value = 0) @PartitionKey(value = 0)
@Column(name = ID_PROPERTY) @Column(name = ID_PROPERTY)
private UUID id; private UUID id;
@ -44,16 +56,15 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
@Column(name = DASHBOARD_TENANT_ID_PROPERTY) @Column(name = DASHBOARD_TENANT_ID_PROPERTY)
private UUID tenantId; private UUID tenantId;
@PartitionKey(value = 2)
@Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)
private UUID customerId;
@Column(name = DASHBOARD_TITLE_PROPERTY) @Column(name = DASHBOARD_TITLE_PROPERTY)
private String title; private String title;
@Column(name = SEARCH_TEXT_PROPERTY) @Column(name = SEARCH_TEXT_PROPERTY)
private String searchText; private String searchText;
@Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
private String assignedCustomers;
public DashboardInfoEntity() { public DashboardInfoEntity() {
super(); super();
} }
@ -65,10 +76,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
if (dashboardInfo.getTenantId() != null) { if (dashboardInfo.getTenantId() != null) {
this.tenantId = dashboardInfo.getTenantId().getId(); this.tenantId = dashboardInfo.getTenantId().getId();
} }
if (dashboardInfo.getCustomerId() != null) {
this.customerId = dashboardInfo.getCustomerId().getId();
}
this.title = dashboardInfo.getTitle(); this.title = dashboardInfo.getTitle();
if (dashboardInfo.getAssignedCustomers() != null) {
try {
this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers());
} catch (JsonProcessingException e) {
log.error("Unable to serialize assigned customers to string!", e);
}
}
} }
public UUID getId() { public UUID getId() {
@ -87,14 +102,6 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
this.tenantId = tenantId; this.tenantId = tenantId;
} }
public UUID getCustomerId() {
return customerId;
}
public void setCustomerId(UUID customerId) {
this.customerId = customerId;
}
public String getTitle() { public String getTitle() {
return title; return title;
} }
@ -103,6 +110,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
this.title = title; this.title = title;
} }
public String getAssignedCustomers() {
return assignedCustomers;
}
public void setAssignedCustomers(String assignedCustomers) {
this.assignedCustomers = assignedCustomers;
}
@Override @Override
public String getSearchTextSource() { public String getSearchTextSource() {
return getTitle(); return getTitle();
@ -124,10 +139,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
if (tenantId != null) { if (tenantId != null) {
dashboardInfo.setTenantId(new TenantId(tenantId)); dashboardInfo.setTenantId(new TenantId(tenantId));
} }
if (customerId != null) {
dashboardInfo.setCustomerId(new CustomerId(customerId));
}
dashboardInfo.setTitle(title); dashboardInfo.setTitle(title);
if (!StringUtils.isEmpty(assignedCustomers)) {
try {
dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
} catch (IOException e) {
log.warn("Unable to parse assigned customers!", e);
}
}
return dashboardInfo; return dashboardInfo;
} }

40
dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java

@ -16,13 +16,18 @@
package org.thingsboard.server.dao.model.sql; package org.thingsboard.server.dao.model.sql;
import com.datastax.driver.core.utils.UUIDs; import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef; import org.hibernate.annotations.TypeDef;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.BaseSqlEntity;
@ -33,26 +38,33 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
import java.io.IOException;
import java.util.HashSet;
@Data @Data
@Slf4j
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Entity @Entity
@TypeDef(name = "json", typeClass = JsonStringType.class) @TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME) @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements SearchTextEntity<Dashboard> { public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements SearchTextEntity<Dashboard> {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final JavaType assignedCustomersType =
objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
@Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY) @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
private String tenantId; private String tenantId;
@Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
private String customerId;
@Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY) @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
private String title; private String title;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText; private String searchText;
@Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
private String assignedCustomers;
@Type(type = "json") @Type(type = "json")
@Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY) @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY)
private JsonNode configuration; private JsonNode configuration;
@ -68,10 +80,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
if (dashboard.getTenantId() != null) { if (dashboard.getTenantId() != null) {
this.tenantId = toString(dashboard.getTenantId().getId()); this.tenantId = toString(dashboard.getTenantId().getId());
} }
if (dashboard.getCustomerId() != null) {
this.customerId = toString(dashboard.getCustomerId().getId());
}
this.title = dashboard.getTitle(); this.title = dashboard.getTitle();
if (dashboard.getAssignedCustomers() != null) {
try {
this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers());
} catch (JsonProcessingException e) {
log.error("Unable to serialize assigned customers to string!", e);
}
}
this.configuration = dashboard.getConfiguration(); this.configuration = dashboard.getConfiguration();
} }
@ -92,10 +108,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
if (tenantId != null) { if (tenantId != null) {
dashboard.setTenantId(new TenantId(toUUID(tenantId))); dashboard.setTenantId(new TenantId(toUUID(tenantId)));
} }
if (customerId != null) {
dashboard.setCustomerId(new CustomerId(toUUID(customerId)));
}
dashboard.setTitle(title); dashboard.setTitle(title);
if (!StringUtils.isEmpty(assignedCustomers)) {
try {
dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
} catch (IOException e) {
log.warn("Unable to parse assigned customers!", e);
}
}
dashboard.setConfiguration(configuration); dashboard.setConfiguration(configuration);
return dashboard; return dashboard;
} }

40
dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java

@ -16,10 +16,15 @@
package org.thingsboard.server.dao.model.sql; package org.thingsboard.server.dao.model.sql;
import com.datastax.driver.core.utils.UUIDs; import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.BaseSqlEntity;
@ -29,25 +34,32 @@ import org.thingsboard.server.dao.model.SearchTextEntity;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
import java.io.IOException;
import java.util.HashSet;
@Data @Data
@Slf4j
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Entity @Entity
@Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME) @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements SearchTextEntity<DashboardInfo> { public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements SearchTextEntity<DashboardInfo> {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final JavaType assignedCustomersType =
objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
@Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY) @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
private String tenantId; private String tenantId;
@Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
private String customerId;
@Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY) @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
private String title; private String title;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText; private String searchText;
@Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
private String assignedCustomers;
public DashboardInfoEntity() { public DashboardInfoEntity() {
super(); super();
} }
@ -59,10 +71,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
if (dashboardInfo.getTenantId() != null) { if (dashboardInfo.getTenantId() != null) {
this.tenantId = toString(dashboardInfo.getTenantId().getId()); this.tenantId = toString(dashboardInfo.getTenantId().getId());
} }
if (dashboardInfo.getCustomerId() != null) {
this.customerId = toString(dashboardInfo.getCustomerId().getId());
}
this.title = dashboardInfo.getTitle(); this.title = dashboardInfo.getTitle();
if (dashboardInfo.getAssignedCustomers() != null) {
try {
this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers());
} catch (JsonProcessingException e) {
log.error("Unable to serialize assigned customers to string!", e);
}
}
} }
@Override @Override
@ -86,10 +102,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
if (tenantId != null) { if (tenantId != null) {
dashboardInfo.setTenantId(new TenantId(toUUID(tenantId))); dashboardInfo.setTenantId(new TenantId(toUUID(tenantId)));
} }
if (customerId != null) {
dashboardInfo.setCustomerId(new CustomerId(toUUID(customerId)));
}
dashboardInfo.setTitle(title); dashboardInfo.setTitle(title);
if (!StringUtils.isEmpty(assignedCustomers)) {
try {
dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
} catch (IOException e) {
log.warn("Unable to parse assigned customers!", e);
}
}
return dashboardInfo; return dashboardInfo;
} }

50
dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service;
import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.page.TimePageLink;
import java.sql.Time;
import java.util.List;
import java.util.UUID;
public abstract class TimePaginatedRemover<I, D extends IdBased<?>> {
private static final int DEFAULT_LIMIT = 100;
public void removeEntities(I id) {
TimePageLink pageLink = new TimePageLink(DEFAULT_LIMIT);
boolean hasNext = true;
while (hasNext) {
List<D> entities = findEntities(id, pageLink);
for (D entity : entities) {
removeEntity(entity);
}
hasNext = entities.size() == pageLink.getLimit();
if (hasNext) {
int index = entities.size() - 1;
UUID idOffset = entities.get(index).getUuidId();
pageLink.setIdOffset(idOffset);
}
}
}
protected abstract List<D> findEntities(I id, TimePageLink pageLink);
protected abstract void removeEntity(D entity);
}

3
dao/src/main/java/org/thingsboard/server/dao/service/Validator.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.BasePageLink;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -116,7 +117,7 @@ public class Validator {
* @param pageLink the page link * @param pageLink the page link
* @param errorMessage the error message for exception * @param errorMessage the error message for exception
*/ */
public static void validatePageLink(TextPageLink pageLink, String errorMessage) { public static void validatePageLink(BasePageLink pageLink, String errorMessage) {
if (pageLink == null || pageLink.getLimit() < 1 || (pageLink.getIdOffset() != null && pageLink.getIdOffset().version() != 1)) { if (pageLink == null || pageLink.getLimit() < 1 || (pageLink.getIdOffset() != null && pageLink.getIdOffset().version() != 1)) {
throw new IncorrectParameterException(errorMessage); throw new IncorrectParameterException(errorMessage);
} }

8
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java

@ -39,12 +39,4 @@ public interface DashboardInfoRepository extends CrudRepository<DashboardInfoEnt
@Param("idOffset") String idOffset, @Param("idOffset") String idOffset,
Pageable pageable); Pageable pageable);
@Query("SELECT di FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId " +
"AND di.customerId = :customerId AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
"AND di.id > :idOffset ORDER BY di.id")
List<DashboardInfoEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
@Param("customerId") String customerId,
@Param("searchText") String searchText,
@Param("idOffset") String idOffset,
Pageable pageable);
} }

36
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java

@ -15,19 +15,31 @@
*/ */
package org.thingsboard.server.dao.sql.dashboard; package org.thingsboard.server.dao.sql.dashboard;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.dashboard.DashboardInfoDao; import org.thingsboard.server.dao.dashboard.DashboardInfoDao;
import org.thingsboard.server.dao.model.sql.DashboardInfoEntity; import org.thingsboard.server.dao.model.sql.DashboardInfoEntity;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
import java.sql.Time;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
@ -37,10 +49,14 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
/** /**
* Created by Valerii Sosliuk on 5/6/2017. * Created by Valerii Sosliuk on 5/6/2017.
*/ */
@Slf4j
@Component @Component
@SqlDao @SqlDao
public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao { public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
@Autowired
private RelationDao relationDao;
@Autowired @Autowired
private DashboardInfoRepository dashboardInfoRepository; private DashboardInfoRepository dashboardInfoRepository;
@ -65,13 +81,17 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
} }
@Override @Override
public List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) { public ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) {
return DaoUtil.convertDataList(dashboardInfoRepository log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
.findByTenantIdAndCustomerId(
UUIDConverter.fromTimeUUID(tenantId), ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink);
UUIDConverter.fromTimeUUID(customerId),
Objects.toString(pageLink.getTextSearch(), ""), return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<DashboardInfo>>) input -> {
pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), List<ListenableFuture<DashboardInfo>> dashboardFutures = new ArrayList<>(input.size());
new PageRequest(0, pageLink.getLimit()))); for (EntityRelation relation : input) {
dashboardFutures.add(findByIdAsync(relation.getTo().getId()));
}
return Futures.successfulAsList(dashboardFutures);
});
} }
} }

17
dao/src/main/resources/cassandra/schema.cql

@ -364,26 +364,19 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.widget_type_by_tenant_and_ali
CREATE TABLE IF NOT EXISTS thingsboard.dashboard ( CREATE TABLE IF NOT EXISTS thingsboard.dashboard (
id timeuuid, id timeuuid,
tenant_id timeuuid, tenant_id timeuuid,
customer_id timeuuid,
title text, title text,
search_text text, search_text text,
assigned_customers text,
configuration text, configuration text,
PRIMARY KEY (id, tenant_id, customer_id) PRIMARY KEY (id, tenant_id)
); );
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
SELECT * SELECT *
from thingsboard.dashboard from thingsboard.dashboard
WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( tenant_id, search_text, id, customer_id ) PRIMARY KEY ( tenant_id, search_text, id )
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC ); WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_customer_and_search_text AS
SELECT *
from thingsboard.dashboard
WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( customer_id, tenant_id, search_text, id )
WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf ( CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf (
entity_type text, // (DEVICE, CUSTOMER, TENANT) entity_type text, // (DEVICE, CUSTOMER, TENANT)

2
dao/src/main/resources/sql/schema.sql

@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS customer (
CREATE TABLE IF NOT EXISTS dashboard ( CREATE TABLE IF NOT EXISTS dashboard (
id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY, id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
configuration varchar(10000000), configuration varchar(10000000),
customer_id varchar(31), assigned_customers varchar(1000000),
search_text varchar(255), search_text varchar(255),
tenant_id varchar(31), tenant_id varchar(31),
title varchar(255) title varchar(255)

104
dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java

@ -29,13 +29,17 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import java.io.IOException; import java.io.IOException;
import java.sql.Time;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException;
public abstract class BaseDashboardServiceTest extends AbstractServiceTest { public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
@ -68,8 +72,6 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
Assert.assertNotNull(savedDashboard.getId()); Assert.assertNotNull(savedDashboard.getId());
Assert.assertTrue(savedDashboard.getCreatedTime() > 0); Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
Assert.assertEquals(dashboard.getTenantId(), savedDashboard.getTenantId()); Assert.assertEquals(dashboard.getTenantId(), savedDashboard.getTenantId());
Assert.assertNotNull(savedDashboard.getCustomerId());
Assert.assertEquals(ModelConstants.NULL_UUID, savedDashboard.getCustomerId().getId());
Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle()); Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
savedDashboard.setTitle("My new dashboard"); savedDashboard.setTitle("My new dashboard");
@ -280,7 +282,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
} }
@Test @Test
public void testFindDashboardsByTenantIdAndCustomerId() { public void testFindDashboardsByTenantIdAndCustomerId() throws ExecutionException, InterruptedException {
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("Test tenant"); tenant.setTitle("Test tenant");
tenant = tenantService.saveTenant(tenant); tenant = tenantService.saveTenant(tenant);
@ -303,10 +305,10 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
} }
List<DashboardInfo> loadedDashboards = new ArrayList<>(); List<DashboardInfo> loadedDashboards = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(23); TimePageLink pageLink = new TimePageLink(23);
TextPageData<DashboardInfo> pageData = null; TimePageData<DashboardInfo> pageData = null;
do { do {
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink); pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
loadedDashboards.addAll(pageData.getData()); loadedDashboards.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -318,98 +320,14 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
Assert.assertEquals(dashboards, loadedDashboards); Assert.assertEquals(dashboards, loadedDashboards);
dashboardService.unassignCustomerDashboards(tenantId, customerId); dashboardService.unassignCustomerDashboards(customerId);
pageLink = new TextPageLink(42); pageLink = new TimePageLink(42);
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink); pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty()); Assert.assertTrue(pageData.getData().isEmpty());
tenantService.deleteTenant(tenantId); tenantService.deleteTenant(tenantId);
} }
@Test
public void testFindDashboardsByTenantIdCustomerIdAndTitle() {
Customer customer = new Customer();
customer.setTitle("Test customer");
customer.setTenantId(tenantId);
customer = customerService.saveCustomer(customer);
CustomerId customerId = customer.getId();
String title1 = "Dashboard title 1";
List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
for (int i=0;i<124;i++) {
Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title1+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title);
dashboard = dashboardService.saveDashboard(dashboard);
dashboardsTitle1.add(new DashboardInfo(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)));
}
String title2 = "Dashboard title 2";
List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
for (int i=0;i<151;i++) {
Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title2+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title);
dashboard = dashboardService.saveDashboard(dashboard);
dashboardsTitle2.add(new DashboardInfo(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)));
}
List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(24, title1);
TextPageData<DashboardInfo> pageData = null;
do {
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
loadedDashboardsTitle1.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Collections.sort(dashboardsTitle1, idComparator);
Collections.sort(loadedDashboardsTitle1, idComparator);
Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
pageLink = new TextPageLink(4, title2);
do {
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
loadedDashboardsTitle2.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Collections.sort(dashboardsTitle2, idComparator);
Collections.sort(loadedDashboardsTitle2, idComparator);
Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
for (DashboardInfo dashboard : loadedDashboardsTitle1) {
dashboardService.deleteDashboard(dashboard.getId());
}
pageLink = new TextPageLink(4, title1);
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
for (DashboardInfo dashboard : loadedDashboardsTitle2) {
dashboardService.deleteDashboard(dashboard.getId());
}
pageLink = new TextPageLink(4, title2);
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
customerService.deleteCustomer(customerId);
}
} }

38
dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.sql.dashboard; package org.thingsboard.server.dao.sql.dashboard;
import com.datastax.driver.core.utils.UUIDs; import com.datastax.driver.core.utils.UUIDs;
import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
@ -40,53 +41,26 @@ public class JpaDashboardInfoDaoTest extends AbstractJpaDaoTest {
@Test @Test
public void testFindDashboardsByTenantId() { public void testFindDashboardsByTenantId() {
UUID tenantId1 = UUIDs.timeBased(); UUID tenantId1 = UUIDs.timeBased();
UUID customerId1 = UUIDs.timeBased();
UUID tenantId2 = UUIDs.timeBased(); UUID tenantId2 = UUIDs.timeBased();
UUID customerId2 = UUIDs.timeBased();
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
createDashboard(tenantId1, customerId1, i); createDashboard(tenantId1, i);
createDashboard(tenantId2, customerId2, i * 2); createDashboard(tenantId2, i * 2);
} }
TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD"); TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD");
List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink1); List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink1);
assertEquals(15, dashboardInfos1.size()); Assert.assertEquals(15, dashboardInfos1.size());
TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null); TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null);
List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink2); List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink2);
assertEquals(5, dashboardInfos2.size()); Assert.assertEquals(5, dashboardInfos2.size());
} }
@Test private void createDashboard(UUID tenantId, int index) {
public void testFindDashboardsByTenantAndCustomerId() {
UUID tenantId1 = UUIDs.timeBased();
UUID customerId1 = UUIDs.timeBased();
UUID tenantId2 = UUIDs.timeBased();
UUID customerId2 = UUIDs.timeBased();
for (int i = 0; i < 20; i++) {
createDashboard(tenantId1, customerId1, i);
createDashboard(tenantId2, customerId2, i * 2);
}
TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD");
List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink1);
assertEquals(15, dashboardInfos1.size());
TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null);
List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink2);
assertEquals(5, dashboardInfos2.size());
}
private void assertEquals(int i, int size) {
}
private void createDashboard(UUID tenantId, UUID customerId, int index) {
DashboardInfo dashboardInfo = new DashboardInfo(); DashboardInfo dashboardInfo = new DashboardInfo();
dashboardInfo.setId(new DashboardId(UUIDs.timeBased())); dashboardInfo.setId(new DashboardId(UUIDs.timeBased()));
dashboardInfo.setTenantId(new TenantId(tenantId)); dashboardInfo.setTenantId(new TenantId(tenantId));
dashboardInfo.setCustomerId(new CustomerId(customerId));
dashboardInfo.setTitle("DASHBOARD_" + index); dashboardInfo.setTitle("DASHBOARD_" + index);
dashboardInfoDao.save(dashboardInfo); dashboardInfoDao.save(dashboardInfo);
} }

150
ui/src/app/api/dashboard.service.js

@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.dashboard', [])
.factory('dashboardService', DashboardService).name; .factory('dashboardService', DashboardService).name;
/*@ngInject*/ /*@ngInject*/
function DashboardService($rootScope, $http, $q, $location, customerService) { function DashboardService($rootScope, $http, $q, $location, $filter) {
var stDiffPromise; var stDiffPromise;
@ -37,7 +37,11 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
deleteDashboard: deleteDashboard, deleteDashboard: deleteDashboard,
saveDashboard: saveDashboard, saveDashboard: saveDashboard,
unassignDashboardFromCustomer: unassignDashboardFromCustomer, unassignDashboardFromCustomer: unassignDashboardFromCustomer,
updateDashboardCustomers: updateDashboardCustomers,
addDashboardCustomers: addDashboardCustomers,
removeDashboardCustomers: removeDashboardCustomers,
makeDashboardPublic: makeDashboardPublic, makeDashboardPublic: makeDashboardPublic,
makeDashboardPrivate: makeDashboardPrivate,
getPublicDashboardLink: getPublicDashboardLink getPublicDashboardLink: getPublicDashboardLink
} }
@ -56,14 +60,14 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
url += '&textOffset=' + pageLink.textOffset; url += '&textOffset=' + pageLink.textOffset;
} }
$http.get(url, config).then(function success(response) { $http.get(url, config).then(function success(response) {
deferred.resolve(response.data); deferred.resolve(prepareDashboards(response.data));
}, function fail() { }, function fail() {
deferred.reject(); deferred.reject();
}); });
return deferred.promise; return deferred.promise;
} }
function getTenantDashboards(pageLink, applyCustomersInfo, config) { function getTenantDashboards(pageLink, config) {
var deferred = $q.defer(); var deferred = $q.defer();
var url = '/api/tenant/dashboards?limit=' + pageLink.limit; var url = '/api/tenant/dashboards?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) { if (angular.isDefined(pageLink.textSearch)) {
@ -76,51 +80,25 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
url += '&textOffset=' + pageLink.textOffset; url += '&textOffset=' + pageLink.textOffset;
} }
$http.get(url, config).then(function success(response) { $http.get(url, config).then(function success(response) {
if (applyCustomersInfo) { deferred.resolve(prepareDashboards(response.data));
customerService.applyAssignedCustomersInfo(response.data.data).then(
function success(data) {
response.data.data = data;
deferred.resolve(response.data);
},
function fail() {
deferred.reject();
}
);
} else {
deferred.resolve(response.data);
}
}, function fail() { }, function fail() {
deferred.reject(); deferred.reject();
}); });
return deferred.promise; return deferred.promise;
} }
function getCustomerDashboards(customerId, pageLink, applyCustomersInfo, config) { function getCustomerDashboards(customerId, pageLink, config) {
var deferred = $q.defer(); var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/dashboards?limit=' + pageLink.limit; var url = '/api/customer/' + customerId + '/dashboards?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
url += '&textSearch=' + pageLink.textSearch;
}
if (angular.isDefined(pageLink.idOffset)) { if (angular.isDefined(pageLink.idOffset)) {
url += '&idOffset=' + pageLink.idOffset; url += '&offset=' + pageLink.idOffset;
}
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
} }
$http.get(url, config).then(function success(response) { $http.get(url, config).then(function success(response) {
if (applyCustomersInfo) { response.data = prepareDashboards(response.data);
customerService.applyAssignedCustomerInfo(response.data.data, customerId).then( if (pageLink.textSearch) {
function success(data) { response.data.data = $filter('filter')(response.data.data, {title: pageLink.textSearch});
response.data.data = data;
deferred.resolve(response.data);
},
function fail() {
deferred.reject();
}
);
} else {
deferred.resolve(response.data);
} }
deferred.resolve(response.data);
}, function fail() { }, function fail() {
deferred.reject(); deferred.reject();
}); });
@ -151,7 +129,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
var deferred = $q.defer(); var deferred = $q.defer();
var url = '/api/dashboard/' + dashboardId; var url = '/api/dashboard/' + dashboardId;
$http.get(url, null).then(function success(response) { $http.get(url, null).then(function success(response) {
deferred.resolve(response.data); deferred.resolve(prepareDashboard(response.data));
}, function fail() { }, function fail() {
deferred.reject(); deferred.reject();
}); });
@ -162,7 +140,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
var deferred = $q.defer(); var deferred = $q.defer();
var url = '/api/dashboard/info/' + dashboardId; var url = '/api/dashboard/info/' + dashboardId;
$http.get(url, config).then(function success(response) { $http.get(url, config).then(function success(response) {
deferred.resolve(response.data); deferred.resolve(prepareDashboard(response.data));
}, function fail() { }, function fail() {
deferred.reject(); deferred.reject();
}); });
@ -172,8 +150,8 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
function saveDashboard(dashboard) { function saveDashboard(dashboard) {
var deferred = $q.defer(); var deferred = $q.defer();
var url = '/api/dashboard'; var url = '/api/dashboard';
$http.post(url, dashboard).then(function success(response) { $http.post(url, cleanDashboard(dashboard)).then(function success(response) {
deferred.resolve(response.data); deferred.resolve(prepareDashboard(response.data));
}, function fail() { }, function fail() {
deferred.reject(); deferred.reject();
}); });
@ -195,18 +173,51 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
var deferred = $q.defer(); var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId; var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
$http.post(url, null).then(function success(response) { $http.post(url, null).then(function success(response) {
deferred.resolve(response.data); deferred.resolve(prepareDashboard(response.data));
}, function fail() { }, function fail() {
deferred.reject(); deferred.reject();
}); });
return deferred.promise; return deferred.promise;
} }
function unassignDashboardFromCustomer(dashboardId) { function unassignDashboardFromCustomer(customerId, dashboardId) {
var deferred = $q.defer(); var deferred = $q.defer();
var url = '/api/customer/dashboard/' + dashboardId; var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
$http.delete(url).then(function success(response) { $http.delete(url).then(function success(response) {
deferred.resolve(response.data); deferred.resolve(prepareDashboard(response.data));
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function updateDashboardCustomers(dashboardId, customerIds) {
var deferred = $q.defer();
var url = '/api/dashboard/' + dashboardId + '/customers';
$http.post(url, customerIds).then(function success(response) {
deferred.resolve(prepareDashboard(response.data));
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function addDashboardCustomers(dashboardId, customerIds) {
var deferred = $q.defer();
var url = '/api/dashboard/' + dashboardId + '/customers/add';
$http.post(url, customerIds).then(function success(response) {
deferred.resolve(prepareDashboard(response.data));
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function removeDashboardCustomers(dashboardId, customerIds) {
var deferred = $q.defer();
var url = '/api/dashboard/' + dashboardId + '/customers/remove';
$http.post(url, customerIds).then(function success(response) {
deferred.resolve(prepareDashboard(response.data));
}, function fail() { }, function fail() {
deferred.reject(); deferred.reject();
}); });
@ -217,7 +228,18 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
var deferred = $q.defer(); var deferred = $q.defer();
var url = '/api/customer/public/dashboard/' + dashboardId; var url = '/api/customer/public/dashboard/' + dashboardId;
$http.post(url, null).then(function success(response) { $http.post(url, null).then(function success(response) {
deferred.resolve(response.data); deferred.resolve(prepareDashboard(response.data));
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function makeDashboardPrivate(dashboardId) {
var deferred = $q.defer();
var url = '/api/customer/public/dashboard/' + dashboardId;
$http.delete(url).then(function success(response) {
deferred.resolve(prepareDashboard(response.data));
}, function fail() { }, function fail() {
deferred.reject(); deferred.reject();
}); });
@ -230,8 +252,44 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
if (port != 80 && port != 443) { if (port != 80 && port != 443) {
url += ":" + port; url += ":" + port;
} }
url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.customerId.id; url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.publicCustomerId;
return url; return url;
} }
function prepareDashboards(dashboardsData) {
if (dashboardsData.data) {
for (var i = 0; i < dashboardsData.data.length; i++) {
dashboardsData.data[i] = prepareDashboard(dashboardsData.data[i]);
}
}
return dashboardsData;
}
function prepareDashboard(dashboard) {
dashboard.publicCustomerId = null;
dashboard.assignedCustomersText = "";
dashboard.assignedCustomersIds = [];
if (dashboard.assignedCustomers && dashboard.assignedCustomers.length) {
var assignedCustomersTitles = [];
for (var i = 0; i < dashboard.assignedCustomers.length; i++) {
var assignedCustomer = dashboard.assignedCustomers[i];
dashboard.assignedCustomersIds.push(assignedCustomer.customerId.id);
if (assignedCustomer.public) {
dashboard.publicCustomerId = assignedCustomer.customerId.id;
} else {
assignedCustomersTitles.push(assignedCustomer.title);
}
}
dashboard.assignedCustomersText = assignedCustomersTitles.join(', ');
}
return dashboard;
}
function cleanDashboard(dashboard) {
delete dashboard.publicCustomerId;
delete dashboard.assignedCustomersText;
delete dashboard.assignedCustomersIds;
return dashboard;
}
} }

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

@ -273,9 +273,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
break; break;
case types.entityType.dashboard: case types.entityType.dashboard:
if (user.authority === 'CUSTOMER_USER') { if (user.authority === 'CUSTOMER_USER') {
promise = dashboardService.getCustomerDashboards(customerId, pageLink, false, config); promise = dashboardService.getCustomerDashboards(customerId, pageLink, config);
} else { } else {
promise = dashboardService.getTenantDashboards(pageLink, false, config); promise = dashboardService.getTenantDashboards(pageLink, config);
} }
break; break;
case types.entityType.user: case types.entityType.user:
@ -403,6 +403,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return deferred.promise; return deferred.promise;
} }
function resolveAliasEntityId(entityType, id) {
var entityId = {
entityType: entityType,
id: id
};
if (entityType == types.aliasEntityType.current_customer) {
var user = userService.getCurrentUser();
entityId.entityType = types.entityType.customer;
if (user.authority === 'CUSTOMER_USER') {
entityId.id = user.customerId;
}
}
return entityId;
}
function getStateEntityId(filter, stateParams) { function getStateEntityId(filter, stateParams) {
var entityId = null; var entityId = null;
if (stateParams) { if (stateParams) {
@ -417,6 +432,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
if (!entityId) { if (!entityId) {
entityId = filter.defaultStateEntity; entityId = filter.defaultStateEntity;
} }
if (entityId) {
entityId = resolveAliasEntityId(entityId.entityType, entityId.id);
}
return entityId; return entityId;
} }
@ -432,7 +450,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
var stateEntityId = getStateEntityId(filter, stateParams); var stateEntityId = getStateEntityId(filter, stateParams);
switch (filter.type) { switch (filter.type) {
case types.aliasFilterType.singleEntity.value: case types.aliasFilterType.singleEntity.value:
getEntity(filter.singleEntity.entityType, filter.singleEntity.id, {ignoreLoading: true}).then( var aliasEntityId = resolveAliasEntityId(filter.singleEntity.entityType, filter.singleEntity.id);
getEntity(aliasEntityId.entityType, aliasEntityId.id, {ignoreLoading: true}).then(
function success(entity) { function success(entity) {
result.entities = entitiesToEntitiesInfo([entity]); result.entities = entitiesToEntitiesInfo([entity]);
deferred.resolve(result); deferred.resolve(result);
@ -530,10 +549,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
rootEntityId = filter.rootEntity.id; rootEntityId = filter.rootEntity.id;
} }
if (rootEntityType && rootEntityId) { if (rootEntityType && rootEntityId) {
var relationQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
var searchQuery = { var searchQuery = {
parameters: { parameters: {
rootId: rootEntityId, rootId: relationQueryRootEntityId.id,
rootType: rootEntityType, rootType: relationQueryRootEntityId.entityType,
direction: filter.direction direction: filter.direction
}, },
filters: filter.filters filters: filter.filters
@ -571,10 +591,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
rootEntityId = filter.rootEntity.id; rootEntityId = filter.rootEntity.id;
} }
if (rootEntityType && rootEntityId) { if (rootEntityType && rootEntityId) {
var searchQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
searchQuery = { searchQuery = {
parameters: { parameters: {
rootId: rootEntityId, rootId: searchQueryRootEntityId.id,
rootType: rootEntityType, rootType: searchQueryRootEntityId.entityType,
direction: filter.direction direction: filter.direction
}, },
relationType: filter.relationType relationType: filter.relationType
@ -709,7 +730,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return result; return result;
} }
function prepareAllowedEntityTypesList(allowedEntityTypes) { function prepareAllowedEntityTypesList(allowedEntityTypes, useAliasEntityTypes) {
var authority = userService.getAuthority(); var authority = userService.getAuthority();
var entityTypes = {}; var entityTypes = {};
switch(authority) { switch(authority) {
@ -726,12 +747,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
entityTypes.rule = types.entityType.rule; entityTypes.rule = types.entityType.rule;
entityTypes.plugin = types.entityType.plugin; entityTypes.plugin = types.entityType.plugin;
entityTypes.dashboard = types.entityType.dashboard; entityTypes.dashboard = types.entityType.dashboard;
if (useAliasEntityTypes) {
entityTypes.current_customer = types.aliasEntityType.current_customer;
}
break; break;
case 'CUSTOMER_USER': case 'CUSTOMER_USER':
entityTypes.device = types.entityType.device; entityTypes.device = types.entityType.device;
entityTypes.asset = types.entityType.asset; entityTypes.asset = types.entityType.asset;
entityTypes.customer = types.entityType.customer; entityTypes.customer = types.entityType.customer;
entityTypes.dashboard = types.entityType.dashboard; entityTypes.dashboard = types.entityType.dashboard;
if (useAliasEntityTypes) {
entityTypes.current_customer = types.aliasEntityType.current_customer;
}
break; break;
} }

4
ui/src/app/api/user.service.js

@ -266,9 +266,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
var pageLink = {limit: 100}; var pageLink = {limit: 100};
var fetchDashboardsPromise; var fetchDashboardsPromise;
if (currentUser.authority === 'TENANT_ADMIN') { if (currentUser.authority === 'TENANT_ADMIN') {
fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink, false); fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink);
} else { } else {
fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink, false); fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink);
} }
fetchDashboardsPromise.then( fetchDashboardsPromise.then(
function success(result) { function success(result) {

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

@ -296,6 +296,9 @@ export default angular.module('thingsboard.types', [])
dashboard: "DASHBOARD", dashboard: "DASHBOARD",
alarm: "ALARM" alarm: "ALARM"
}, },
aliasEntityType: {
current_customer: "CURRENT_CUSTOMER"
},
entityTypeTranslations: { entityTypeTranslations: {
"DEVICE": { "DEVICE": {
type: 'entity.type-device', type: 'entity.type-device',
@ -350,6 +353,10 @@ export default angular.module('thingsboard.types', [])
typePlural: 'entity.type-alarms', typePlural: 'entity.type-alarms',
list: 'entity.list-of-alarms', list: 'entity.list-of-alarms',
nameStartsWith: 'entity.alarm-name-starts-with' nameStartsWith: 'entity.alarm-name-starts-with'
},
"CURRENT_CUSTOMER": {
type: 'entity.type-current-customer',
list: 'entity.type-current-customer'
} }
}, },
entitySearchDirection: { entitySearchDirection: {

4
ui/src/app/components/dashboard-autocomplete.directive.js

@ -48,7 +48,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
var promise; var promise;
if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') { if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
if (scope.customerId) { if (scope.customerId) {
promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false, {ignoreLoading: true}); promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, {ignoreLoading: true});
} else { } else {
promise = $q.when({data: []}); promise = $q.when({data: []});
} }
@ -60,7 +60,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
promise = $q.when({data: []}); promise = $q.when({data: []});
} }
} else { } else {
promise = dashboardService.getTenantDashboards(pageLink, false, {ignoreLoading: true}); promise = dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true});
} }
} }

4
ui/src/app/components/dashboard-select.directive.js

@ -48,12 +48,12 @@ function DashboardSelect($compile, $templateCache, $q, $mdMedia, $mdPanel, $docu
var promise; var promise;
if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') { if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
if (scope.customerId && scope.customerId != types.id.nullUid) { if (scope.customerId && scope.customerId != types.id.nullUid) {
promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false, {ignoreLoading: true}); promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, {ignoreLoading: true});
} else { } else {
promise = $q.when({data: []}); promise = $q.when({data: []});
} }
} else { } else {
promise = dashboardService.getTenantDashboards(pageLink, false, {ignoreLoading: true}); promise = dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true});
} }
promise.then(function success(result) { promise.then(function success(result) {

2
ui/src/app/dashboard/add-dashboards-to-customer.controller.js

@ -52,7 +52,7 @@ export default function AddDashboardsToCustomerController(dashboardService, $mdD
fetchMoreItems_: function () { fetchMoreItems_: function () {
if (vm.dashboards.hasNext && !vm.dashboards.pending) { if (vm.dashboards.hasNext && !vm.dashboards.pending) {
vm.dashboards.pending = true; vm.dashboards.pending = true;
dashboardService.getTenantDashboards(vm.dashboards.nextPageLink, false).then( dashboardService.getTenantDashboards(vm.dashboards.nextPageLink).then(
function success(dashboards) { function success(dashboards) {
vm.dashboards.data = vm.dashboards.data.concat(dashboards.data); vm.dashboards.data = vm.dashboards.data.concat(dashboards.data);
vm.dashboards.nextPageLink = dashboards.nextPageLink; vm.dashboards.nextPageLink = dashboards.nextPageLink;

123
ui/src/app/dashboard/assign-to-customer.controller.js

@ -1,123 +0,0 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*@ngInject*/
export default function AssignDashboardToCustomerController(customerService, dashboardService, $mdDialog, $q, dashboardIds, customers) {
var vm = this;
vm.customers = customers;
vm.searchText = '';
vm.assign = assign;
vm.cancel = cancel;
vm.isCustomerSelected = isCustomerSelected;
vm.hasData = hasData;
vm.noData = noData;
vm.searchCustomerTextUpdated = searchCustomerTextUpdated;
vm.toggleCustomerSelection = toggleCustomerSelection;
vm.theCustomers = {
getItemAtIndex: function (index) {
if (index > vm.customers.data.length) {
vm.theCustomers.fetchMoreItems_(index);
return null;
}
var item = vm.customers.data[index];
if (item) {
item.indexNumber = index + 1;
}
return item;
},
getLength: function () {
if (vm.customers.hasNext) {
return vm.customers.data.length + vm.customers.nextPageLink.limit;
} else {
return vm.customers.data.length;
}
},
fetchMoreItems_: function () {
if (vm.customers.hasNext && !vm.customers.pending) {
vm.customers.pending = true;
customerService.getCustomers(vm.customers.nextPageLink).then(
function success(customers) {
vm.customers.data = vm.customers.data.concat(customers.data);
vm.customers.nextPageLink = customers.nextPageLink;
vm.customers.hasNext = customers.hasNext;
if (vm.customers.hasNext) {
vm.customers.nextPageLink.limit = vm.customers.pageSize;
}
vm.customers.pending = false;
},
function fail() {
vm.customers.hasNext = false;
vm.customers.pending = false;
});
}
}
};
function cancel () {
$mdDialog.cancel();
}
function assign () {
var tasks = [];
for (var dashboardId in dashboardIds) {
tasks.push(dashboardService.assignDashboardToCustomer(vm.customers.selection.id.id, dashboardIds[dashboardId]));
}
$q.all(tasks).then(function () {
$mdDialog.hide();
});
}
function noData () {
return vm.customers.data.length == 0 && !vm.customers.hasNext;
}
function hasData () {
return vm.customers.data.length > 0;
}
function toggleCustomerSelection ($event, customer) {
$event.stopPropagation();
if (vm.isCustomerSelected(customer)) {
vm.customers.selection = null;
} else {
vm.customers.selection = customer;
}
}
function isCustomerSelected (customer) {
return vm.customers.selection != null && customer &&
customer.id.id === vm.customers.selection.id.id;
}
function searchCustomerTextUpdated () {
vm.customers = {
pageSize: vm.customers.pageSize,
data: [],
nextPageLink: {
limit: vm.customers.pageSize,
textSearch: vm.searchText
},
selection: null,
hasNext: true,
pending: false
};
}
}

76
ui/src/app/dashboard/assign-to-customer.tpl.html

@ -1,76 +0,0 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog aria-label="{{ 'dashboard.assign-dashboard-to-customer' | translate }}">
<form name="theForm" ng-submit="vm.assign()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>dashboard.assign-dashboard-to-customer</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<fieldset>
<span translate>dashboard.assign-to-customer-text</span>
<md-input-container class="md-block" style='margin-bottom: 0px;'>
<label>&nbsp;</label>
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
search
</md-icon>
<input id="customer-search" autofocus ng-model="vm.searchText"
ng-change="vm.searchCustomerTextUpdated()"
placeholder="{{ 'common.enter-search' | translate }}"/>
</md-input-container>
<div style='min-height: 150px;'>
<span translate layout-align="center center"
style="text-transform: uppercase; display: flex; height: 150px;"
class="md-subhead"
ng-show="vm.noData()">customer.no-customers-text</span>
<md-virtual-repeat-container ng-show="vm.hasData()"
tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
style='min-height: 150px; width: 100%;'>
<md-list>
<md-list-item md-virtual-repeat="customer in vm.theCustomers" md-on-demand
class="repeated-item" flex>
<md-checkbox ng-click="vm.toggleCustomerSelection($event, customer)"
aria-label="{{ 'item.selected' | translate }}"
ng-checked="vm.isCustomerSelected(customer)"></md-checkbox>
<span> {{ customer.title }} </span>
</md-list-item>
</md-list>
</md-virtual-repeat-container>
</div>
</fieldset>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="$root.loading || vm.customers.selection==null" type="submit" class="md-raised md-primary">
{{ 'action.assign' | translate }}
</md-button>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
translate }}
</md-button>
</md-dialog-actions>
</form>
</md-dialog>

27
ui/src/app/dashboard/dashboard-card.scss

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-dashboard-assigned-customers {
display: block;
display: -webkit-box;
height: 34px;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
margin-bottom: 4px;
}

4
ui/src/app/dashboard/dashboard-card.tpl.html

@ -15,6 +15,6 @@
limitations under the License. limitations under the License.
--> -->
<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'dashboard.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div> <div class="tb-small tb-dashboard-assigned-customers" ng-show="vm.parentCtl.dashboardsScope === 'tenant' && vm.item.assignedCustomersText">{{'dashboard.assignedToCustomers' | translate}}: '{{vm.item.assignedCustomersText}}'</div>
<div class="tb-small" ng-show="vm.isPublic()">{{'dashboard.public' | translate}}</div> <div class="tb-small" ng-show="vm.parentCtl.dashboardsScope === 'tenant' && vm.item.publicCustomerId">{{'dashboard.public' | translate}}</div>

27
ui/src/app/dashboard/dashboard-fieldset.tpl.html

@ -19,24 +19,29 @@
ng-show="!isEdit && dashboardScope === 'tenant'" ng-show="!isEdit && dashboardScope === 'tenant'"
class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button> class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button>
<md-button ng-click="onMakePublic({event: $event})" <md-button ng-click="onMakePublic({event: $event})"
ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer && !isPublic" ng-show="!isEdit && dashboardScope === 'tenant' && !dashboard.publicCustomerId"
class="md-raised md-primary">{{ 'dashboard.make-public' | translate }}</md-button> class="md-raised md-primary">{{ 'dashboard.make-public' | translate }}</md-button>
<md-button ng-click="onAssignToCustomer({event: $event})" <md-button ng-click="onMakePrivate({event: $event})"
ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer" ng-show="!isEdit && ((dashboardScope === 'tenant' && dashboard.publicCustomerId ||
class="md-raised md-primary">{{ 'dashboard.assign-to-customer' | translate }}</md-button> dashboardScope === 'customer' && customerId == dashboard.publicCustomerId))"
<md-button ng-click="onUnassignFromCustomer({event: $event, isPublic: isPublic})" class="md-raised md-primary">{{ 'dashboard.make-private' | translate }}</md-button>
ng-show="!isEdit && (dashboardScope === 'customer' || dashboardScope === 'tenant') && isAssignedToCustomer" <md-button ng-click="onManageAssignedCustomers({event: $event})"
class="md-raised md-primary">{{ isPublic ? 'dashboard.make-private' : 'dashboard.unassign-from-customer' | translate }}</md-button> ng-show="!isEdit && dashboardScope === 'tenant'"
class="md-raised md-primary">{{ 'dashboard.manage-assigned-customers' | translate }}</md-button>
<md-button ng-click="onUnassignFromCustomer({event: $event})"
ng-show="!isEdit && dashboardScope === 'customer' && customerId != dashboard.publicCustomerId"
class="md-raised md-primary">{{ 'dashboard.unassign-from-customer' | translate }}</md-button>
<md-button ng-click="onDeleteDashboard({event: $event})" <md-button ng-click="onDeleteDashboard({event: $event})"
ng-show="!isEdit && dashboardScope === 'tenant'" ng-show="!isEdit && dashboardScope === 'tenant'"
class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button> class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button>
<md-content class="md-padding" layout="column"> <md-content class="md-padding" layout="column">
<md-input-container class="md-block" <md-input-container class="md-block"
ng-show="!isEdit && isAssignedToCustomer && !isPublic && dashboardScope === 'tenant'"> ng-show="!isEdit && dashboard.assignedCustomersText && dashboardScope === 'tenant'">
<label translate>dashboard.assignedToCustomer</label> <label translate>dashboard.assignedToCustomers</label>
<input ng-model="assignedCustomer.title" disabled> <input ng-model="dashboard.assignedCustomersText" disabled>
</md-input-container> </md-input-container>
<div layout="column" ng-show="!isEdit && isPublic && (dashboardScope === 'customer' || dashboardScope === 'tenant')"> <div layout="column" ng-show="!isEdit && ((dashboardScope === 'tenant' && dashboard.publicCustomerId ||
dashboardScope === 'customer' && customerId == dashboard.publicCustomerId))">
<tb-social-share-panel style="padding-bottom: 10px;" <tb-social-share-panel style="padding-bottom: 10px;"
share-title="{{ 'dashboard.socialshare-title' | translate:{dashboardTitle: dashboard.title} }}" share-title="{{ 'dashboard.socialshare-title' | translate:{dashboardTitle: dashboard.title} }}"
share-text="{{ 'dashboard.socialshare-text' | translate:{dashboardTitle: dashboard.title} }}" share-text="{{ 'dashboard.socialshare-text' | translate:{dashboardTitle: dashboard.title} }}"

29
ui/src/app/dashboard/dashboard.directive.js

@ -20,36 +20,17 @@ import dashboardFieldsetTemplate from './dashboard-fieldset.tpl.html';
/* eslint-enable import/no-unresolved, import/default */ /* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/ /*@ngInject*/
export default function DashboardDirective($compile, $templateCache, $translate, types, toast, customerService, dashboardService) { export default function DashboardDirective($compile, $templateCache, $translate, types, toast, dashboardService) {
var linker = function (scope, element) { var linker = function (scope, element) {
var template = $templateCache.get(dashboardFieldsetTemplate); var template = $templateCache.get(dashboardFieldsetTemplate);
element.html(template); element.html(template);
scope.isAssignedToCustomer = false;
scope.isPublic = false;
scope.assignedCustomer = null;
scope.publicLink = null; scope.publicLink = null;
scope.$watch('dashboard', function(newVal) { scope.$watch('dashboard', function(newVal) {
if (newVal) { if (newVal) {
if (scope.dashboard.customerId && scope.dashboard.customerId.id !== types.id.nullUid) { if (scope.dashboard.publicCustomerId) {
scope.isAssignedToCustomer = true; scope.publicLink = dashboardService.getPublicDashboardLink(scope.dashboard);
customerService.getShortCustomerInfo(scope.dashboard.customerId.id).then(
function success(customer) {
scope.assignedCustomer = customer;
scope.isPublic = customer.isPublic;
if (scope.isPublic) {
scope.publicLink = dashboardService.getPublicDashboardLink(scope.dashboard);
} else {
scope.publicLink = null;
}
}
);
} else { } else {
scope.isAssignedToCustomer = false;
scope.isPublic = false;
scope.publicLink = null; scope.publicLink = null;
scope.assignedCustomer = null;
} }
} }
}); });
@ -66,10 +47,12 @@ export default function DashboardDirective($compile, $templateCache, $translate,
scope: { scope: {
dashboard: '=', dashboard: '=',
isEdit: '=', isEdit: '=',
customerId: '=',
dashboardScope: '=', dashboardScope: '=',
theForm: '=', theForm: '=',
onAssignToCustomer: '&',
onMakePublic: '&', onMakePublic: '&',
onMakePrivate: '&',
onManageAssignedCustomers: '&',
onUnassignFromCustomer: '&', onUnassignFromCustomer: '&',
onExportDashboard: '&', onExportDashboard: '&',
onDeleteDashboard: '&' onDeleteDashboard: '&'

226
ui/src/app/dashboard/dashboards.controller.js

@ -17,12 +17,14 @@
import addDashboardTemplate from './add-dashboard.tpl.html'; import addDashboardTemplate from './add-dashboard.tpl.html';
import dashboardCard from './dashboard-card.tpl.html'; import dashboardCard from './dashboard-card.tpl.html';
import assignToCustomerTemplate from './assign-to-customer.tpl.html';
import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.html'; import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.html';
import makeDashboardPublicDialogTemplate from './make-dashboard-public-dialog.tpl.html'; import makeDashboardPublicDialogTemplate from './make-dashboard-public-dialog.tpl.html';
import manageAssignedCustomersTemplate from './manage-assigned-customers.tpl.html';
/* eslint-enable import/no-unresolved, import/default */ /* eslint-enable import/no-unresolved, import/default */
import './dashboard-card.scss';
/*@ngInject*/ /*@ngInject*/
export function MakeDashboardPublicDialogController($mdDialog, $translate, toast, dashboardService, dashboard) { export function MakeDashboardPublicDialogController($mdDialog, $translate, toast, dashboardService, dashboard) {
@ -48,23 +50,8 @@ export function MakeDashboardPublicDialogController($mdDialog, $translate, toast
export function DashboardCardController(types) { export function DashboardCardController(types) {
var vm = this; var vm = this;
vm.types = types; vm.types = types;
vm.isAssignedToCustomer = function() {
if (vm.item && vm.item.customerId && vm.parentCtl.dashboardsScope === 'tenant' &&
vm.item.customerId.id != vm.types.id.nullUid && !vm.item.assignedCustomer.isPublic) {
return true;
}
return false;
}
vm.isPublic = function() {
if (vm.item && vm.item.assignedCustomer && vm.parentCtl.dashboardsScope === 'tenant' && vm.item.assignedCustomer.isPublic) {
return true;
}
return false;
}
} }
/*@ngInject*/ /*@ngInject*/
@ -135,8 +122,9 @@ export function DashboardsController(userService, dashboardService, customerServ
vm.dashboardsScope = $state.$current.data.dashboardsType; vm.dashboardsScope = $state.$current.data.dashboardsType;
vm.assignToCustomer = assignToCustomer;
vm.makePublic = makePublic; vm.makePublic = makePublic;
vm.makePrivate = makePrivate;
vm.manageAssignedCustomers = manageAssignedCustomers;
vm.unassignFromCustomer = unassignFromCustomer; vm.unassignFromCustomer = unassignFromCustomer;
vm.exportDashboard = exportDashboard; vm.exportDashboard = exportDashboard;
@ -155,6 +143,7 @@ export function DashboardsController(userService, dashboardService, customerServ
} }
if (customerId) { if (customerId) {
vm.customerId = customerId;
vm.customerDashboardsTitle = $translate.instant('customer.dashboards'); vm.customerDashboardsTitle = $translate.instant('customer.dashboards');
customerService.getShortCustomerInfo(customerId).then( customerService.getShortCustomerInfo(customerId).then(
function success(info) { function success(info) {
@ -167,7 +156,7 @@ export function DashboardsController(userService, dashboardService, customerServ
if (vm.dashboardsScope === 'tenant') { if (vm.dashboardsScope === 'tenant') {
fetchDashboardsFunction = function (pageLink) { fetchDashboardsFunction = function (pageLink) {
return dashboardService.getTenantDashboards(pageLink, true); return dashboardService.getTenantDashboards(pageLink);
}; };
deleteDashboardFunction = function (dashboardId) { deleteDashboardFunction = function (dashboardId) {
return dashboardService.deleteDashboard(dashboardId); return dashboardService.deleteDashboard(dashboardId);
@ -194,11 +183,33 @@ export function DashboardsController(userService, dashboardService, customerServ
details: function() { return $translate.instant('dashboard.make-public') }, details: function() { return $translate.instant('dashboard.make-public') },
icon: "share", icon: "share",
isEnabled: function(dashboard) { isEnabled: function(dashboard) {
return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid); return dashboard && !dashboard.publicCustomerId;
} }
}); });
dashboardActionsList.push({ dashboardActionsList.push({
onAction: function ($event, item) {
makePrivate($event, item);
},
name: function() { return $translate.instant('action.make-private') },
details: function() { return $translate.instant('dashboard.make-private') },
icon: "reply",
isEnabled: function(dashboard) {
return dashboard && dashboard.publicCustomerId;
}
});
dashboardActionsList.push({
onAction: function ($event, item) {
manageAssignedCustomers($event, item);
},
name: function() { return $translate.instant('action.assign') },
details: function() { return $translate.instant('dashboard.manage-assigned-customers') },
icon: "assignment_ind",
isEnabled: function(dashboard) {
return dashboard;
}
});
/*dashboardActionsList.push({
onAction: function ($event, item) { onAction: function ($event, item) {
assignToCustomer($event, [ item.id.id ]); assignToCustomer($event, [ item.id.id ]);
}, },
@ -208,8 +219,8 @@ export function DashboardsController(userService, dashboardService, customerServ
isEnabled: function(dashboard) { isEnabled: function(dashboard) {
return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid); return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid);
} }
}); });*/
dashboardActionsList.push({ /*dashboardActionsList.push({
onAction: function ($event, item) { onAction: function ($event, item) {
unassignFromCustomer($event, item, false); unassignFromCustomer($event, item, false);
}, },
@ -219,18 +230,7 @@ export function DashboardsController(userService, dashboardService, customerServ
isEnabled: function(dashboard) { isEnabled: function(dashboard) {
return dashboard && dashboard.customerId && dashboard.customerId.id !== types.id.nullUid && !dashboard.assignedCustomer.isPublic; return dashboard && dashboard.customerId && dashboard.customerId.id !== types.id.nullUid && !dashboard.assignedCustomer.isPublic;
} }
}); });*/
dashboardActionsList.push({
onAction: function ($event, item) {
unassignFromCustomer($event, item, true);
},
name: function() { return $translate.instant('action.make-private') },
details: function() { return $translate.instant('dashboard.make-private') },
icon: "reply",
isEnabled: function(dashboard) {
return dashboard && dashboard.customerId && dashboard.customerId.id !== types.id.nullUid && dashboard.assignedCustomer.isPublic;
}
});
dashboardActionsList.push( dashboardActionsList.push(
{ {
@ -246,7 +246,7 @@ export function DashboardsController(userService, dashboardService, customerServ
dashboardGroupActionsList.push( dashboardGroupActionsList.push(
{ {
onAction: function ($event, items) { onAction: function ($event, items) {
assignDashboardsToCustomer($event, items); assignDashboardsToCustomers($event, items);
}, },
name: function() { return $translate.instant('dashboard.assign-dashboards') }, name: function() { return $translate.instant('dashboard.assign-dashboards') },
details: function(selectedCount) { details: function(selectedCount) {
@ -255,6 +255,17 @@ export function DashboardsController(userService, dashboardService, customerServ
icon: "assignment_ind" icon: "assignment_ind"
} }
); );
dashboardGroupActionsList.push(
{
onAction: function ($event, items) {
unassignDashboardsFromCustomers($event, items);
},
name: function() { return $translate.instant('dashboard.unassign-dashboards') },
details: function(selectedCount) {
return $translate.instant('dashboard.unassign-dashboards-action-text', {count: selectedCount}, "messageformat");
},
icon: "assignment_return" }
);
dashboardGroupActionsList.push( dashboardGroupActionsList.push(
{ {
@ -290,10 +301,10 @@ export function DashboardsController(userService, dashboardService, customerServ
}); });
} else if (vm.dashboardsScope === 'customer' || vm.dashboardsScope === 'customer_user') { } else if (vm.dashboardsScope === 'customer' || vm.dashboardsScope === 'customer_user') {
fetchDashboardsFunction = function (pageLink) { fetchDashboardsFunction = function (pageLink) {
return dashboardService.getCustomerDashboards(customerId, pageLink, true); return dashboardService.getCustomerDashboards(customerId, pageLink);
}; };
deleteDashboardFunction = function (dashboardId) { deleteDashboardFunction = function (dashboardId) {
return dashboardService.unassignDashboardFromCustomer(dashboardId); return dashboardService.unassignDashboardFromCustomer(customerId, dashboardId);
}; };
refreshDashboardsParamsFunction = function () { refreshDashboardsParamsFunction = function () {
return {"customerId": customerId, "topIndex": vm.topIndex}; return {"customerId": customerId, "topIndex": vm.topIndex};
@ -314,26 +325,27 @@ export function DashboardsController(userService, dashboardService, customerServ
dashboardActionsList.push( dashboardActionsList.push(
{ {
onAction: function ($event, item) { onAction: function ($event, item) {
unassignFromCustomer($event, item, false); makePrivate($event, item);
}, },
name: function() { return $translate.instant('action.unassign') }, name: function() { return $translate.instant('action.make-private') },
details: function() { return $translate.instant('dashboard.unassign-from-customer') }, details: function() { return $translate.instant('dashboard.make-private') },
icon: "assignment_return", icon: "reply",
isEnabled: function(dashboard) { isEnabled: function(dashboard) {
return dashboard && !dashboard.assignedCustomer.isPublic; return dashboard && customerId == dashboard.publicCustomerId;
} }
} }
); );
dashboardActionsList.push( dashboardActionsList.push(
{ {
onAction: function ($event, item) { onAction: function ($event, item) {
unassignFromCustomer($event, item, true); unassignFromCustomer($event, item, customerId);
}, },
name: function() { return $translate.instant('action.make-private') }, name: function() { return $translate.instant('action.unassign') },
details: function() { return $translate.instant('dashboard.make-private') }, details: function() { return $translate.instant('dashboard.unassign-from-customer') },
icon: "reply", icon: "assignment_return",
isEnabled: function(dashboard) { isEnabled: function(dashboard) {
return dashboard && dashboard.assignedCustomer.isPublic; return dashboard && customerId != dashboard.publicCustomerId;
} }
} }
); );
@ -341,7 +353,7 @@ export function DashboardsController(userService, dashboardService, customerServ
dashboardGroupActionsList.push( dashboardGroupActionsList.push(
{ {
onAction: function ($event, items) { onAction: function ($event, items) {
unassignDashboardsFromCustomer($event, items); unassignDashboardsFromCustomer($event, items, customerId);
}, },
name: function() { return $translate.instant('dashboard.unassign-dashboards') }, name: function() { return $translate.instant('dashboard.unassign-dashboards') },
details: function(selectedCount) { details: function(selectedCount) {
@ -351,7 +363,6 @@ export function DashboardsController(userService, dashboardService, customerServ
} }
); );
vm.dashboardGridConfig.addItemAction = { vm.dashboardGridConfig.addItemAction = {
onAction: function ($event) { onAction: function ($event) {
addDashboardsToCustomer($event); addDashboardsToCustomer($event);
@ -428,39 +439,42 @@ export function DashboardsController(userService, dashboardService, customerServ
return deferred.promise; return deferred.promise;
} }
function assignToCustomer($event, dashboardIds) { function manageAssignedCustomers($event, dashboard) {
showManageAssignedCustomersDialog($event, [dashboard.id.id], 'manage', dashboard.assignedCustomersIds);
}
function assignDashboardsToCustomers($event, items) {
var dashboardIds = [];
for (var id in items.selections) {
dashboardIds.push(id);
}
showManageAssignedCustomersDialog($event, dashboardIds, 'assign');
}
function unassignDashboardsFromCustomers($event, items) {
var dashboardIds = [];
for (var id in items.selections) {
dashboardIds.push(id);
}
showManageAssignedCustomersDialog($event, dashboardIds, 'unassign');
}
function showManageAssignedCustomersDialog($event, dashboardIds, actionType, assignedCustomers) {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
var pageSize = 10; $mdDialog.show({
customerService.getCustomers({limit: pageSize, textSearch: ''}).then( controller: 'ManageAssignedCustomersController',
function success(_customers) { controllerAs: 'vm',
var customers = { templateUrl: manageAssignedCustomersTemplate,
pageSize: pageSize, locals: {actionType: actionType, dashboardIds: dashboardIds, assignedCustomers: assignedCustomers},
data: _customers.data, parent: angular.element($document[0].body),
nextPageLink: _customers.nextPageLink, fullscreen: true,
selection: null, targetEvent: $event
hasNext: _customers.hasNext, }).then(function () {
pending: false vm.grid.refreshList();
}; }, function () {
if (customers.hasNext) { });
customers.nextPageLink.limit = pageSize;
}
$mdDialog.show({
controller: 'AssignDashboardToCustomerController',
controllerAs: 'vm',
templateUrl: assignToCustomerTemplate,
locals: {dashboardIds: dashboardIds, customers: customers},
parent: angular.element($document[0].body),
fullscreen: true,
targetEvent: $event
}).then(function () {
vm.grid.refreshList();
}, function () {
});
},
function fail() {
});
} }
function addDashboardsToCustomer($event) { function addDashboardsToCustomer($event) {
@ -468,7 +482,7 @@ export function DashboardsController(userService, dashboardService, customerServ
$event.stopPropagation(); $event.stopPropagation();
} }
var pageSize = 10; var pageSize = 10;
dashboardService.getTenantDashboards({limit: pageSize, textSearch: ''}, false).then( dashboardService.getTenantDashboards({limit: pageSize, textSearch: ''}).then(
function success(_dashboards) { function success(_dashboards) {
var dashboards = { var dashboards = {
pageSize: pageSize, pageSize: pageSize,
@ -499,30 +513,13 @@ export function DashboardsController(userService, dashboardService, customerServ
}); });
} }
function assignDashboardsToCustomer($event, items) { function unassignFromCustomer($event, dashboard, customerId) {
var dashboardIds = [];
for (var id in items.selections) {
dashboardIds.push(id);
}
assignToCustomer($event, dashboardIds);
}
function unassignFromCustomer($event, dashboard, isPublic) {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
var title; var title = $translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title});
var content; var content = $translate.instant('dashboard.unassign-dashboard-text');
var label; var label = $translate.instant('dashboard.unassign-dashboard');
if (isPublic) {
title = $translate.instant('dashboard.make-private-dashboard-title', {dashboardTitle: dashboard.title});
content = $translate.instant('dashboard.make-private-dashboard-text');
label = $translate.instant('dashboard.make-private-dashboard');
} else {
title = $translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title});
content = $translate.instant('dashboard.unassign-dashboard-text');
label = $translate.instant('dashboard.unassign-dashboard');
}
var confirm = $mdDialog.confirm() var confirm = $mdDialog.confirm()
.targetEvent($event) .targetEvent($event)
.title(title) .title(title)
@ -531,7 +528,7 @@ export function DashboardsController(userService, dashboardService, customerServ
.cancel($translate.instant('action.no')) .cancel($translate.instant('action.no'))
.ok($translate.instant('action.yes')); .ok($translate.instant('action.yes'));
$mdDialog.show(confirm).then(function () { $mdDialog.show(confirm).then(function () {
dashboardService.unassignDashboardFromCustomer(dashboard.id.id).then(function success() { dashboardService.unassignDashboardFromCustomer(customerId, dashboard.id.id).then(function success() {
vm.grid.refreshList(); vm.grid.refreshList();
}); });
}); });
@ -556,12 +553,33 @@ export function DashboardsController(userService, dashboardService, customerServ
}); });
} }
function makePrivate($event, dashboard) {
if ($event) {
$event.stopPropagation();
}
var title = $translate.instant('dashboard.make-private-dashboard-title', {dashboardTitle: dashboard.title});
var content = $translate.instant('dashboard.make-private-dashboard-text');
var label = $translate.instant('dashboard.make-private-dashboard');
var confirm = $mdDialog.confirm()
.targetEvent($event)
.title(title)
.htmlContent(content)
.ariaLabel(label)
.cancel($translate.instant('action.no'))
.ok($translate.instant('action.yes'));
$mdDialog.show(confirm).then(function () {
dashboardService.makeDashboardPrivate(dashboard.id.id).then(function success() {
vm.grid.refreshList();
});
});
}
function exportDashboard($event, dashboard) { function exportDashboard($event, dashboard) {
$event.stopPropagation(); $event.stopPropagation();
importExport.exportDashboard(dashboard.id.id); importExport.exportDashboard(dashboard.id.id);
} }
function unassignDashboardsFromCustomer($event, items) { function unassignDashboardsFromCustomer($event, items, customerId) {
var confirm = $mdDialog.confirm() var confirm = $mdDialog.confirm()
.targetEvent($event) .targetEvent($event)
.title($translate.instant('dashboard.unassign-dashboards-title', {count: items.selectedCount}, 'messageformat')) .title($translate.instant('dashboard.unassign-dashboards-title', {count: items.selectedCount}, 'messageformat'))
@ -572,7 +590,7 @@ export function DashboardsController(userService, dashboardService, customerServ
$mdDialog.show(confirm).then(function () { $mdDialog.show(confirm).then(function () {
var tasks = []; var tasks = [];
for (var id in items.selections) { for (var id in items.selections) {
tasks.push(dashboardService.unassignDashboardFromCustomer(id)); tasks.push(dashboardService.unassignDashboardFromCustomer(customerId, id));
} }
$q.all(tasks).then(function () { $q.all(tasks).then(function () {
vm.grid.refreshList(); vm.grid.refreshList();

6
ui/src/app/dashboard/dashboards.tpl.html

@ -25,10 +25,12 @@
<tb-dashboard-details dashboard="vm.grid.operatingItem()" <tb-dashboard-details dashboard="vm.grid.operatingItem()"
is-edit="vm.grid.detailsConfig.isDetailsEditMode" is-edit="vm.grid.detailsConfig.isDetailsEditMode"
dashboard-scope="vm.dashboardsScope" dashboard-scope="vm.dashboardsScope"
customer-id="vm.customerId"
the-form="vm.grid.detailsForm" the-form="vm.grid.detailsForm"
on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)" on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)"
on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)" on-make-private="vm.makePrivate(event, vm.grid.detailsConfig.currentItem)"
on-manage-assigned-customers="vm.manageAssignedCustomers(event, vm.grid.detailsConfig.currentItem)"
on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, vm.customerId)"
on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)" on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)"
on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details> on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details>
</md-tab> </md-tab>

4
ui/src/app/dashboard/index.js

@ -40,8 +40,8 @@ import DashboardRoutes from './dashboard.routes';
import {DashboardsController, DashboardCardController, MakeDashboardPublicDialogController} from './dashboards.controller'; import {DashboardsController, DashboardCardController, MakeDashboardPublicDialogController} from './dashboards.controller';
import DashboardController from './dashboard.controller'; import DashboardController from './dashboard.controller';
import DashboardSettingsController from './dashboard-settings.controller'; import DashboardSettingsController from './dashboard-settings.controller';
import AssignDashboardToCustomerController from './assign-to-customer.controller';
import AddDashboardsToCustomerController from './add-dashboards-to-customer.controller'; import AddDashboardsToCustomerController from './add-dashboards-to-customer.controller';
import ManageAssignedCustomersController from './manage-assigned-customers.controller';
import AddWidgetController from './add-widget.controller'; import AddWidgetController from './add-widget.controller';
import DashboardDirective from './dashboard.directive'; import DashboardDirective from './dashboard.directive';
import EditWidgetDirective from './edit-widget.directive'; import EditWidgetDirective from './edit-widget.directive';
@ -74,8 +74,8 @@ export default angular.module('thingsboard.dashboard', [
.controller('MakeDashboardPublicDialogController', MakeDashboardPublicDialogController) .controller('MakeDashboardPublicDialogController', MakeDashboardPublicDialogController)
.controller('DashboardController', DashboardController) .controller('DashboardController', DashboardController)
.controller('DashboardSettingsController', DashboardSettingsController) .controller('DashboardSettingsController', DashboardSettingsController)
.controller('AssignDashboardToCustomerController', AssignDashboardToCustomerController)
.controller('AddDashboardsToCustomerController', AddDashboardsToCustomerController) .controller('AddDashboardsToCustomerController', AddDashboardsToCustomerController)
.controller('ManageAssignedCustomersController', ManageAssignedCustomersController)
.controller('AddWidgetController', AddWidgetController) .controller('AddWidgetController', AddWidgetController)
.directive('tbDashboardDetails', DashboardDirective) .directive('tbDashboardDetails', DashboardDirective)
.directive('tbEditWidget', EditWidgetDirective) .directive('tbEditWidget', EditWidgetDirective)

69
ui/src/app/dashboard/manage-assigned-customers.controller.js

@ -0,0 +1,69 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*@ngInject*/
export default function ManageAssignedCustomersController($mdDialog, $q, types, dashboardService, actionType, dashboardIds, assignedCustomers) {
var vm = this;
vm.types = types;
vm.actionType = actionType;
vm.dashboardIds = dashboardIds;
vm.assignedCustomers = assignedCustomers;
if (actionType != 'manage') {
vm.assignedCustomers = [];
}
if (actionType == 'manage') {
vm.titleText = 'dashboard.manage-assigned-customers';
vm.labelText = 'dashboard.assigned-customers';
vm.actionName = 'action.update';
} else if (actionType == 'assign') {
vm.titleText = 'dashboard.assign-to-customers';
vm.labelText = 'dashboard.assign-to-customers-text';
vm.actionName = 'action.assign';
} else if (actionType == 'unassign') {
vm.titleText = 'dashboard.unassign-from-customers';
vm.labelText = 'dashboard.unassign-from-customers-text';
vm.actionName = 'action.unassign';
}
vm.submit = submit;
vm.cancel = cancel;
function cancel () {
$mdDialog.cancel();
}
function submit () {
var tasks = [];
for (var i=0;i<vm.dashboardIds.length;i++) {
var dashboardId = vm.dashboardIds[i];
var promise;
if (vm.actionType == 'manage') {
promise = dashboardService.updateDashboardCustomers(dashboardId, vm.assignedCustomers);
} else if (vm.actionType == 'assign') {
promise = dashboardService.addDashboardCustomers(dashboardId, vm.assignedCustomers);
} else if (vm.actionType == 'unassign') {
promise = dashboardService.removeDashboardCustomers(dashboardId, vm.assignedCustomers);
}
tasks.push(promise);
}
$q.all(tasks).then(function () {
$mdDialog.hide();
});
}
}

51
ui/src/app/dashboard/manage-assigned-customers.tpl.html

@ -0,0 +1,51 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog aria-label="{{ vm.titleText | translate }}" style="width: 600px;">
<form name="theForm" ng-submit="vm.submit()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>{{vm.titleText}}</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<fieldset>
<span translate>{{vm.labelText}}</span>
<tb-entity-list ng-disabled="$root.loading"
ng-model="vm.assignedCustomers"
entity-type="vm.types.entityType.customer"></tb-entity-list>
</fieldset>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="$root.loading || !theForm.$dirty || !theForm.$valid" type="submit" class="md-raised md-primary">
{{ vm.actionName | translate }}
</md-button>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
translate }}
</md-button>
</md-dialog-actions>
</form>
</md-dialog>

37
ui/src/app/entity/entity-autocomplete.directive.js

@ -38,7 +38,12 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
if (scope.excludeEntityIds && scope.excludeEntityIds.length) { if (scope.excludeEntityIds && scope.excludeEntityIds.length) {
limit += scope.excludeEntityIds.length; limit += scope.excludeEntityIds.length;
} }
entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit, {ignoreLoading: true}, scope.entitySubtype).then(function success(result) { var targetType = scope.entityType;
if (targetType == types.aliasEntityType.current_customer) {
targetType = types.entityType.customer;
}
entityService.getEntitiesByNameFilter(targetType, searchText, limit, {ignoreLoading: true}, scope.entitySubtype).then(function success(result) {
if (result) { if (result) {
if (scope.excludeEntityIds && scope.excludeEntityIds.length) { if (scope.excludeEntityIds && scope.excludeEntityIds.length) {
var entities = []; var entities = [];
@ -71,7 +76,11 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
ngModelCtrl.$render = function () { ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) { if (ngModelCtrl.$viewValue) {
entityService.getEntity(scope.entityType, ngModelCtrl.$viewValue).then( var targetType = scope.entityType;
if (targetType == types.aliasEntityType.current_customer) {
targetType = types.entityType.customer;
}
entityService.getEntity(targetType, ngModelCtrl.$viewValue).then(
function success(entity) { function success(entity) {
scope.entity = entity; scope.entity = entity;
}, },
@ -114,55 +123,61 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
scope.selectEntityText = 'asset.select-asset'; scope.selectEntityText = 'asset.select-asset';
scope.entityText = 'asset.asset'; scope.entityText = 'asset.asset';
scope.noEntitiesMatchingText = 'asset.no-assets-matching'; scope.noEntitiesMatchingText = 'asset.no-assets-matching';
scope.entityRequiredText = 'asset.asset-required' scope.entityRequiredText = 'asset.asset-required';
break; break;
case types.entityType.device: case types.entityType.device:
scope.selectEntityText = 'device.select-device'; scope.selectEntityText = 'device.select-device';
scope.entityText = 'device.device'; scope.entityText = 'device.device';
scope.noEntitiesMatchingText = 'device.no-devices-matching'; scope.noEntitiesMatchingText = 'device.no-devices-matching';
scope.entityRequiredText = 'device.device-required' scope.entityRequiredText = 'device.device-required';
break; break;
case types.entityType.rule: case types.entityType.rule:
scope.selectEntityText = 'rule.select-rule'; scope.selectEntityText = 'rule.select-rule';
scope.entityText = 'rule.rule'; scope.entityText = 'rule.rule';
scope.noEntitiesMatchingText = 'rule.no-rules-matching'; scope.noEntitiesMatchingText = 'rule.no-rules-matching';
scope.entityRequiredText = 'rule.rule-required' scope.entityRequiredText = 'rule.rule-required';
break; break;
case types.entityType.plugin: case types.entityType.plugin:
scope.selectEntityText = 'plugin.select-plugin'; scope.selectEntityText = 'plugin.select-plugin';
scope.entityText = 'plugin.plugin'; scope.entityText = 'plugin.plugin';
scope.noEntitiesMatchingText = 'plugin.no-plugins-matching'; scope.noEntitiesMatchingText = 'plugin.no-plugins-matching';
scope.entityRequiredText = 'plugin.plugin-required' scope.entityRequiredText = 'plugin.plugin-required';
break; break;
case types.entityType.tenant: case types.entityType.tenant:
scope.selectEntityText = 'tenant.select-tenant'; scope.selectEntityText = 'tenant.select-tenant';
scope.entityText = 'tenant.tenant'; scope.entityText = 'tenant.tenant';
scope.noEntitiesMatchingText = 'tenant.no-tenants-matching'; scope.noEntitiesMatchingText = 'tenant.no-tenants-matching';
scope.entityRequiredText = 'tenant.tenant-required' scope.entityRequiredText = 'tenant.tenant-required';
break; break;
case types.entityType.customer: case types.entityType.customer:
scope.selectEntityText = 'customer.select-customer'; scope.selectEntityText = 'customer.select-customer';
scope.entityText = 'customer.customer'; scope.entityText = 'customer.customer';
scope.noEntitiesMatchingText = 'customer.no-customers-matching'; scope.noEntitiesMatchingText = 'customer.no-customers-matching';
scope.entityRequiredText = 'customer.customer-required' scope.entityRequiredText = 'customer.customer-required';
break; break;
case types.entityType.user: case types.entityType.user:
scope.selectEntityText = 'user.select-user'; scope.selectEntityText = 'user.select-user';
scope.entityText = 'user.user'; scope.entityText = 'user.user';
scope.noEntitiesMatchingText = 'user.no-users-matching'; scope.noEntitiesMatchingText = 'user.no-users-matching';
scope.entityRequiredText = 'user.user-required' scope.entityRequiredText = 'user.user-required';
break; break;
case types.entityType.dashboard: case types.entityType.dashboard:
scope.selectEntityText = 'dashboard.select-dashboard'; scope.selectEntityText = 'dashboard.select-dashboard';
scope.entityText = 'dashboard.dashboard'; scope.entityText = 'dashboard.dashboard';
scope.noEntitiesMatchingText = 'dashboard.no-dashboards-matching'; scope.noEntitiesMatchingText = 'dashboard.no-dashboards-matching';
scope.entityRequiredText = 'dashboard.dashboard-required' scope.entityRequiredText = 'dashboard.dashboard-required';
break; break;
case types.entityType.alarm: case types.entityType.alarm:
scope.selectEntityText = 'alarm.select-alarm'; scope.selectEntityText = 'alarm.select-alarm';
scope.entityText = 'alarm.alarm'; scope.entityText = 'alarm.alarm';
scope.noEntitiesMatchingText = 'alarm.no-alarms-matching'; scope.noEntitiesMatchingText = 'alarm.no-alarms-matching';
scope.entityRequiredText = 'alarm.alarm-required' scope.entityRequiredText = 'alarm.alarm-required';
break;
case types.aliasEntityType.current_customer:
scope.selectEntityText = 'customer.select-default-customer';
scope.entityText = 'customer.default-customer';
scope.noEntitiesMatchingText = 'customer.no-customers-matching';
scope.entityRequiredText = 'customer.default-customer-required';
break; break;
} }
if (scope.entity && scope.entity.id.entityType != scope.entityType) { if (scope.entity && scope.entity.id.entityType != scope.entityType) {

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

@ -32,6 +32,7 @@
<tb-entity-select flex <tb-entity-select flex
the-form="theForm" the-form="theForm"
tb-required="true" tb-required="true"
use-alias-entity-types="true"
ng-model="filter.singleEntity"> ng-model="filter.singleEntity">
</tb-entity-select> </tb-entity-select>
</section> </section>
@ -78,6 +79,7 @@
<tb-entity-select flex <tb-entity-select flex
the-form="theForm" the-form="theForm"
tb-required="false" tb-required="false"
use-alias-entity-types="true"
ng-model="filter.defaultStateEntity"> ng-model="filter.defaultStateEntity">
</tb-entity-select> </tb-entity-select>
</div> </div>
@ -123,6 +125,7 @@
the-form="theForm" the-form="theForm"
tb-required="!filter.rootStateEntity" tb-required="!filter.rootStateEntity"
ng-disabled="filter.rootStateEntity" ng-disabled="filter.rootStateEntity"
use-alias-entity-types="true"
ng-model="filter.rootEntity"> ng-model="filter.rootEntity">
</tb-entity-select> </tb-entity-select>
</div> </div>
@ -139,6 +142,7 @@
<tb-entity-select flex <tb-entity-select flex
the-form="theForm" the-form="theForm"
tb-required="false" tb-required="false"
use-alias-entity-types="true"
ng-model="filter.defaultStateEntity"> ng-model="filter.defaultStateEntity">
</tb-entity-select> </tb-entity-select>
</div> </div>
@ -182,6 +186,7 @@
the-form="theForm" the-form="theForm"
tb-required="!filter.rootStateEntity" tb-required="!filter.rootStateEntity"
ng-disabled="filter.rootStateEntity" ng-disabled="filter.rootStateEntity"
use-alias-entity-types="true"
ng-model="filter.rootEntity"> ng-model="filter.rootEntity">
</tb-entity-select> </tb-entity-select>
</div> </div>
@ -198,6 +203,7 @@
<tb-entity-select flex <tb-entity-select flex
the-form="theForm" the-form="theForm"
tb-required="false" tb-required="false"
use-alias-entity-types="true"
ng-model="filter.defaultStateEntity"> ng-model="filter.defaultStateEntity">
</tb-entity-select> </tb-entity-select>
</div> </div>
@ -249,6 +255,7 @@
the-form="theForm" the-form="theForm"
tb-required="!filter.rootStateEntity" tb-required="!filter.rootStateEntity"
ng-disabled="filter.rootStateEntity" ng-disabled="filter.rootStateEntity"
use-alias-entity-types="true"
ng-model="filter.rootEntity"> ng-model="filter.rootEntity">
</tb-entity-select> </tb-entity-select>
</div> </div>
@ -265,6 +272,7 @@
<tb-entity-select flex <tb-entity-select flex
the-form="theForm" the-form="theForm"
tb-required="false" tb-required="false"
use-alias-entity-types="true"
ng-model="filter.defaultStateEntity"> ng-model="filter.defaultStateEntity">
</tb-entity-select> </tb-entity-select>
</div> </div>

3
ui/src/app/entity/entity-select.directive.js

@ -105,7 +105,8 @@ export default function EntitySelect($compile, $templateCache) {
scope: { scope: {
theForm: '=?', theForm: '=?',
tbRequired: '=?', tbRequired: '=?',
disabled:'=ngDisabled' disabled:'=ngDisabled',
useAliasEntityTypes: "=?"
} }
}; };
} }

1
ui/src/app/entity/entity-select.tpl.html

@ -20,6 +20,7 @@
the-form="theForm" the-form="theForm"
ng-disabled="disabled" ng-disabled="disabled"
tb-required="tbRequired" tb-required="tbRequired"
use-alias-entity-types="useAliasEntityTypes"
ng-model="model.entityType"> ng-model="model.entityType">
</tb-entity-type-select> </tb-entity-type-select>
<tb-entity-autocomplete flex ng-if="model.entityType" <tb-entity-autocomplete flex ng-if="model.entityType"

5
ui/src/app/entity/entity-type-select.directive.js

@ -39,7 +39,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity
scope.ngModelCtrl = ngModelCtrl; scope.ngModelCtrl = ngModelCtrl;
scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes); scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes, scope.useAliasEntityTypes);
scope.typeName = function(type) { scope.typeName = function(type) {
return type ? types.entityTypeTranslations[type].type : ''; return type ? types.entityTypeTranslations[type].type : '';
@ -79,7 +79,8 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity
theForm: '=?', theForm: '=?',
tbRequired: '=?', tbRequired: '=?',
disabled:'=ngDisabled', disabled:'=ngDisabled',
allowedEntityTypes: "=?" allowedEntityTypes: "=?",
useAliasEntityTypes: "=?"
} }
}; };
} }

11
ui/src/app/import-export/import-export.service.js

@ -540,7 +540,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
function success(dashboard) { function success(dashboard) {
var name = dashboard.title; var name = dashboard.title;
name = name.toLowerCase().replace(/\W/g,"_"); name = name.toLowerCase().replace(/\W/g,"_");
exportToPc(prepareExport(dashboard), name + '.json'); exportToPc(prepareDashboardExport(dashboard), name + '.json');
}, },
function fail(rejection) { function fail(rejection) {
var message = rejection; var message = rejection;
@ -552,6 +552,15 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
); );
} }
function prepareDashboardExport(dashboard) {
dashboard = prepareExport(dashboard);
delete dashboard.assignedCustomers;
delete dashboard.publicCustomerId;
delete dashboard.assignedCustomersText;
delete dashboard.assignedCustomersIds;
return dashboard;
}
function importDashboard($event) { function importDashboard($event) {
var deferred = $q.defer(); var deferred = $q.defer();
openImportDialog($event, 'dashboard.import', 'dashboard.dashboard-file').then( openImportDialog($event, 'dashboard.import', 'dashboard.dashboard-file').then(

16
ui/src/app/locale/locale.constant.js

@ -383,7 +383,10 @@ export default angular.module('thingsboard.locale', [])
"idCopiedMessage": "Customer Id has been copied to clipboard", "idCopiedMessage": "Customer Id has been copied to clipboard",
"select-customer": "Select customer", "select-customer": "Select customer",
"no-customers-matching": "No customers matching '{{entity}}' were found.", "no-customers-matching": "No customers matching '{{entity}}' were found.",
"customer-required": "Customer is required" "customer-required": "Customer is required",
"select-default-customer": "Select default customer",
"default-customer": "Default customer",
"default-customer-required": "Default customer is required in order to debug dashboard on Tenant level"
}, },
"datetime": { "datetime": {
"date-from": "Date from", "date-from": "Date from",
@ -404,6 +407,12 @@ export default angular.module('thingsboard.locale', [])
"unassign-from-customer": "Unassign from customer", "unassign-from-customer": "Unassign from customer",
"make-public": "Make dashboard public", "make-public": "Make dashboard public",
"make-private": "Make dashboard private", "make-private": "Make dashboard private",
"manage-assigned-customers": "Manage assigned customers",
"assigned-customers": "Assigned customers",
"assign-to-customers": "Assign Dashboard(s) To Customers",
"assign-to-customers-text": "Please select the customers to assign the dashboard(s)",
"unassign-from-customers": "Unassign Dashboard(s) From Customers",
"unassign-from-customers-text": "Please select the customers to unassign from the dashboard(s)",
"no-dashboards-text": "No dashboards found", "no-dashboards-text": "No dashboards found",
"no-widgets": "No widgets configured", "no-widgets": "No widgets configured",
"add-widget": "Add new widget", "add-widget": "Add new widget",
@ -418,7 +427,8 @@ export default angular.module('thingsboard.locale', [])
"add-dashboard-text": "Add new dashboard", "add-dashboard-text": "Add new dashboard",
"assign-dashboards": "Assign dashboards", "assign-dashboards": "Assign dashboards",
"assign-new-dashboard": "Assign new dashboard", "assign-new-dashboard": "Assign new dashboard",
"assign-dashboards-text": "Assign { count, select, 1 {1 dashboard} other {# dashboards} } to customer", "assign-dashboards-text": "Assign { count, select, 1 {1 dashboard} other {# dashboards} } to customers",
"unassign-dashboards-action-text": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customers",
"delete-dashboards": "Delete dashboards", "delete-dashboards": "Delete dashboards",
"unassign-dashboards": "Unassign dashboards", "unassign-dashboards": "Unassign dashboards",
"unassign-dashboards-action-title": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customer", "unassign-dashboards-action-title": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customer",
@ -500,6 +510,7 @@ export default angular.module('thingsboard.locale', [])
"Please contact your administrator in order to resolve this issue.", "Please contact your administrator in order to resolve this issue.",
"select-devices": "Select devices", "select-devices": "Select devices",
"assignedToCustomer": "Assigned to customer", "assignedToCustomer": "Assigned to customer",
"assignedToCustomers": "Assigned to customers",
"public": "Public", "public": "Public",
"public-link": "Public link", "public-link": "Public link",
"copy-public-link": "Copy public link", "copy-public-link": "Copy public link",
@ -735,6 +746,7 @@ export default angular.module('thingsboard.locale', [])
"type-alarms": "Alarms", "type-alarms": "Alarms",
"list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }", "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
"alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'", "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
"type-current-customer": "Current Customer",
"search": "Search entities", "search": "Search entities",
"selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected", "selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
"entity-name": "Entity name", "entity-name": "Entity name",

Loading…
Cancel
Save