Browse Source

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

Feature/customer dashboards
pull/625/head
Igor Kulikov 8 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)
) WITH CLUSTERING ORDER BY ( partition ASC )
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)
);
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 {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
Dashboard dashboard = dashboardService.findDashboardById(dashboardId);
checkDashboard(dashboard, true);
checkDashboard(dashboard);
return dashboard;
} catch (Exception e) {
throw handleException(e, false);
@ -434,28 +434,23 @@ public abstract class BaseController {
try {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId);
SecurityUser authUser = getCurrentUser();
checkDashboard(dashboardInfo, authUser.getAuthority() != Authority.SYS_ADMIN);
checkDashboard(dashboardInfo);
return dashboardInfo;
} catch (Exception e) {
throw handleException(e, false);
}
}
private void checkDashboard(DashboardInfo dashboard, boolean checkCustomerId) throws ThingsboardException {
private void checkDashboard(DashboardInfo dashboard) throws ThingsboardException {
checkNotNull(dashboard);
checkTenantId(dashboard.getTenantId());
SecurityUser authUser = getCurrentUser();
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,
ThingsboardErrorCode.PERMISSION_DENIED);
}
}
if (checkCustomerId &&
dashboard.getCustomerId() != null && !dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
checkCustomerId(dashboard.getCustomerId());
}
}
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.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.Customer;
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.*;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
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.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException;
import java.util.HashSet;
import java.util.Set;
@RestController
@RequestMapping("/api")
public class DashboardController extends BaseController {
@ -80,7 +82,7 @@ public class DashboardController extends BaseController {
Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard));
logEntityAction(savedDashboard.getId(), savedDashboard,
savedDashboard.getCustomerId(),
null,
dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedDashboard;
@ -103,7 +105,7 @@ public class DashboardController extends BaseController {
dashboardService.deleteDashboard(dashboardId);
logEntityAction(dashboardId, dashboard,
dashboard.getCustomerId(),
null,
ActionType.DELETED, null, strDashboardId);
} catch (Exception e) {
@ -134,7 +136,7 @@ public class DashboardController extends BaseController {
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
logEntityAction(dashboardId, savedDashboard,
savedDashboard.getCustomerId(),
customerId,
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName());
@ -150,23 +152,22 @@ public class DashboardController extends BaseController {
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@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);
try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
Customer customer = checkCustomerId(customerId);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
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));
Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
logEntityAction(dashboardId, dashboard,
dashboard.getCustomerId(),
customerId,
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName());
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')")
@RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody
@ -192,7 +345,7 @@ public class DashboardController extends BaseController {
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId()));
logEntityAction(dashboardId, savedDashboard,
savedDashboard.getCustomerId(),
publicCustomer.getId(),
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
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')")
@RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody
@ -245,19 +425,20 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody
public TextPageData<DashboardInfo> getCustomerDashboards(
public TimePageData<DashboardInfo> getCustomerDashboards(
@PathVariable("customerId") String strCustomerId,
@RequestParam int limit,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime,
@RequestParam(required = false, defaultValue = "false") boolean ascOrder,
@RequestParam(required = false) String offset) throws ThingsboardException {
checkParameter("customerId", strCustomerId);
try {
TenantId tenantId = getCurrentUser().getTenantId();
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get());
} catch (Exception 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.thingsboard.server.dao.cassandra.CassandraCluster;
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.service.install.cql.CQLStatementsParser;
import org.thingsboard.server.service.install.cql.CassandraDbHelper;
@ -33,6 +34,8 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import static org.thingsboard.server.service.install.DatabaseHelper.*;
@Service
@NoSqlDao
@Profile("install")
@ -40,12 +43,6 @@ import java.util.List;
public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
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}")
private String dataDir;
@ -56,6 +53,9 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
@Autowired
private CassandraInstallCluster installCluster;
@Autowired
private DashboardService dashboardService;
@Override
public void upgradeDatabase(String fromVersion) throws Exception {
@ -160,10 +160,32 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
case "1.3.0":
break;
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 ...");
schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
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;
default:
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());
Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
dashboard.setTenantId(tenantId);
dashboard.setCustomerId(customerId);
dashboardService.saveDashboard(dashboard);
Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
if (customerId != null && !customerId.isNullUid()) {
dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId);
}
} catch (Exception e) {
log.error("Unable to load dashboard from json: [{}]", path.toString());
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;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.dashboard.DashboardService;
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.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import static org.thingsboard.server.service.install.DatabaseHelper.*;
import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION;
@Service
@Profile("install")
@Slf4j
@ -49,6 +57,9 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
@Value("${spring.datasource.password}")
private String dbPassword;
@Autowired
private DashboardService dashboardService;
@Override
public void upgradeDatabase(String fromVersion) throws Exception {
switch (fromVersion) {
@ -62,13 +73,30 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Schema updated.");
break;
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)) {
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"));
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;
default:
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.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,
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) {
Path dumpFile = Files.createTempFile(dumpPrefix, null);
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);
stmt.setFetchSize(1000);
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 {
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);
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 -> {
BoundStatement boundStatement = prepared.bind();
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.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Collections;
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.page.TextPageData;
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.dao.model.ModelConstants;
import org.junit.After;
@ -82,8 +85,6 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Assert.assertNotNull(savedDashboard.getId());
Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId());
Assert.assertNotNull(savedDashboard.getCustomerId());
Assert.assertEquals(NULL_UUID, savedDashboard.getCustomerId().getId());
Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
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/" + 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);
Assert.assertEquals(savedCustomer.getId(), foundDashboard.getCustomerId());
Assert.assertTrue(foundDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo()));
Dashboard unassignedDashboard =
doDelete("/api/customer/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDashboard.getCustomerId().getId());
doDelete("/api/customer/"+savedCustomer.getId().getId().toString()+"/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertTrue(unassignedDashboard.getAssignedCustomers() == null || unassignedDashboard.getAssignedCustomers().isEmpty());
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
@ -320,11 +324,11 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
}
List<DashboardInfo> loadedDashboards = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(21);
TextPageData<DashboardInfo> pageData = null;
TimePageLink pageLink = new TimePageLink(21);
TimePageData<DashboardInfo> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
pageData = doGetTypedWithTimePageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TimePageData<DashboardInfo>>(){}, pageLink);
loadedDashboards.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
@ -336,93 +340,5 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
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;
}
@JsonIgnore
public ShortCustomerInfo toShortCustomerInfo() {
return new ShortCustomerInfo(id, title, isPublic());
}
@Override
@JsonProperty(access = Access.READ_ONLY)
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();
builder.append("Dashboard [tenantId=");
builder.append(getTenantId());
builder.append(", customerId=");
builder.append(getCustomerId());
builder.append(", title=");
builder.append(getTitle());
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.TenantId;
import java.util.*;
public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName {
private TenantId tenantId;
private CustomerId customerId;
private String title;
private Set<ShortCustomerInfo> assignedCustomers;
public DashboardInfo() {
super();
@ -37,8 +39,8 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
public DashboardInfo(DashboardInfo dashboardInfo) {
super(dashboardInfo);
this.tenantId = dashboardInfo.getTenantId();
this.customerId = dashboardInfo.getCustomerId();
this.title = dashboardInfo.getTitle();
this.assignedCustomers = dashboardInfo.getAssignedCustomers();
}
public TenantId getTenantId() {
@ -49,14 +51,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.tenantId = tenantId;
}
public CustomerId getCustomerId() {
return customerId;
}
public void setCustomerId(CustomerId customerId) {
this.customerId = customerId;
}
public String getTitle() {
return title;
}
@ -65,6 +59,62 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
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
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() {
@ -80,7 +130,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
result = prime * result + ((title == null) ? 0 : title.hashCode());
return result;
@ -95,11 +144,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
if (getClass() != obj.getClass())
return false;
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 (other.tenantId != null)
return false;
@ -118,8 +162,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
StringBuilder builder = new StringBuilder();
builder.append("DashboardInfo [tenantId=");
builder.append(tenantId);
builder.append(", customerId=");
builder.append(customerId);
builder.append(", title=");
builder.append(title);
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 {
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) {
log.trace("Executing saveCustomer [{}]", customer);
customerValidator.validate(customer);
return customerDao.save(customer);
Customer savedCustomer = customerDao.save(customer);
dashboardService.updateCustomerDashboards(savedCustomer.getId());
return savedCustomer;
}
@Override
@ -108,7 +110,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
if (customer == null) {
throw new IncorrectParameterException("Unable to delete non-existent customer.");
}
dashboardService.unassignCustomerDashboards(customer.getTenantId(), customerId);
dashboardService.unassignCustomerDashboards(customerId);
assetService.unassignCustomerAssets(customer.getTenantId(), customerId);
deviceService.unassignCustomerDevices(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;
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.stereotype.Component;
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.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.model.nosql.DashboardInfoEntity;
import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.util.NoSqlDao;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@ -37,6 +47,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
@NoSqlDao
public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
@Autowired
private RelationDao relationDao;
@Override
protected Class<DashboardInfoEntity> getColumnFamilyClass() {
return DashboardInfoEntity.class;
@ -59,15 +72,18 @@ public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<Da
}
@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);
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);
return DaoUtil.convertDataList(dashboardEntities);
ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink);
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;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.Dao;
import java.util.List;
@ -44,6 +46,6 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> {
* @param pageLink the page link
* @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.page.TextPageData;
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 {
@ -38,7 +42,7 @@ public interface DashboardService {
Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId);
Dashboard unassignDashboardFromCustomer(DashboardId dashboardId);
Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId);
void deleteDashboard(DashboardId dashboardId);
@ -46,8 +50,10 @@ public interface DashboardService {
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;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
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.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
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.PaginatedRemover;
import org.thingsboard.server.dao.service.TimePaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
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.Set;
import java.util.concurrent.ExecutionException;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -59,7 +71,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Autowired
private CustomerDao customerDao;
@Override
public Dashboard findDashboardById(DashboardId dashboardId) {
log.trace("Executing findDashboardById [{}]", dashboardId);
@ -98,15 +110,63 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Override
public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId) {
Dashboard dashboard = findDashboardById(dashboardId);
dashboard.setCustomerId(customerId);
return saveDashboard(dashboard);
Customer customer = customerDao.findById(customerId.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!");
}
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
public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId) {
public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId) {
Dashboard dashboard = findDashboardById(dashboardId);
dashboard.setCustomerId(null);
return saveDashboard(dashboard);
Customer customer = customerDao.findById(customerId.getId());
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
@ -134,23 +194,44 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
}
@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);
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
Validator.validateId(customerId, "Incorrect customerId " + customerId);
Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<DashboardInfo> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
return new TextPageData<>(dashboards, pageLink);
ListenableFuture<List<DashboardInfo>> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), 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
public void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId) {
log.trace("Executing unassignCustomerDashboards, tenantId [{}], customerId [{}]", tenantId, customerId);
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
public void unassignCustomerDashboards(CustomerId customerId) {
log.trace("Executing unassignCustomerDashboards, 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 =
new DataValidator<Dashboard>() {
@Override
@ -166,17 +247,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
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 TenantId tenantId;
private class CustomerDashboardsUnassigner extends TimePaginatedRemover<Customer, DashboardInfo> {
CustomerDashboardsUnassigner(TenantId tenantId) {
this.tenantId = tenantId;
private Customer customer;
CustomerDashboardsUnassigner(Customer customer) {
this.customer = customer;
}
@Override
protected List<DashboardInfo> findEntities(CustomerId id, TextPageLink pageLink) {
return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
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) {
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_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_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_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_customer_and_search_text";
/**
* 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.PartitionKey;
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.ObjectMapper;
import lombok.EqualsAndHashCode;
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.id.CustomerId;
import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.SearchTextEntity;
import org.thingsboard.server.dao.model.type.JsonCodec;
import java.io.IOException;
import java.util.HashSet;
import java.util.UUID;
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)
@EqualsAndHashCode
@ToString
@Slf4j
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)
@Column(name = ID_PROPERTY)
private UUID id;
@ -46,16 +58,15 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
@Column(name = DASHBOARD_TENANT_ID_PROPERTY)
private UUID tenantId;
@PartitionKey(value = 2)
@Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)
private UUID customerId;
@Column(name = DASHBOARD_TITLE_PROPERTY)
private String title;
@Column(name = SEARCH_TEXT_PROPERTY)
private String searchText;
@Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
private String assignedCustomers;
@Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
private JsonNode configuration;
@ -70,10 +81,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
if (dashboard.getTenantId() != null) {
this.tenantId = dashboard.getTenantId().getId();
}
if (dashboard.getCustomerId() != null) {
this.customerId = dashboard.getCustomerId().getId();
}
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();
}
@ -93,14 +108,6 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
this.tenantId = tenantId;
}
public UUID getCustomerId() {
return customerId;
}
public void setCustomerId(UUID customerId) {
this.customerId = customerId;
}
public String getTitle() {
return title;
}
@ -109,6 +116,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
this.title = title;
}
public String getAssignedCustomers() {
return assignedCustomers;
}
public void setAssignedCustomers(String assignedCustomers) {
this.assignedCustomers = assignedCustomers;
}
public JsonNode getConfiguration() {
return configuration;
}
@ -138,10 +153,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
if (tenantId != null) {
dashboard.setTenantId(new TenantId(tenantId));
}
if (customerId != null) {
dashboard.setCustomerId(new CustomerId(customerId));
}
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);
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.PartitionKey;
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.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
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.TenantId;
import org.thingsboard.server.dao.model.SearchTextEntity;
import java.io.IOException;
import java.util.HashSet;
import java.util.UUID;
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)
@EqualsAndHashCode
@ToString
@Slf4j
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)
@Column(name = ID_PROPERTY)
private UUID id;
@ -44,16 +56,15 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
@Column(name = DASHBOARD_TENANT_ID_PROPERTY)
private UUID tenantId;
@PartitionKey(value = 2)
@Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)
private UUID customerId;
@Column(name = DASHBOARD_TITLE_PROPERTY)
private String title;
@Column(name = SEARCH_TEXT_PROPERTY)
private String searchText;
@Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
private String assignedCustomers;
public DashboardInfoEntity() {
super();
}
@ -65,10 +76,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
if (dashboardInfo.getTenantId() != null) {
this.tenantId = dashboardInfo.getTenantId().getId();
}
if (dashboardInfo.getCustomerId() != null) {
this.customerId = dashboardInfo.getCustomerId().getId();
}
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() {
@ -87,14 +102,6 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
this.tenantId = tenantId;
}
public UUID getCustomerId() {
return customerId;
}
public void setCustomerId(UUID customerId) {
this.customerId = customerId;
}
public String getTitle() {
return title;
}
@ -103,6 +110,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
this.title = title;
}
public String getAssignedCustomers() {
return assignedCustomers;
}
public void setAssignedCustomers(String assignedCustomers) {
this.assignedCustomers = assignedCustomers;
}
@Override
public String getSearchTextSource() {
return getTitle();
@ -124,10 +139,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
if (tenantId != null) {
dashboardInfo.setTenantId(new TenantId(tenantId));
}
if (customerId != null) {
dashboardInfo.setCustomerId(new CustomerId(customerId));
}
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;
}

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

@ -16,13 +16,18 @@
package org.thingsboard.server.dao.model.sql;
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.ObjectMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.springframework.util.StringUtils;
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.TenantId;
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.Entity;
import javax.persistence.Table;
import java.io.IOException;
import java.util.HashSet;
@Data
@Slf4j
@EqualsAndHashCode(callSuper = true)
@Entity
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
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)
private String tenantId;
@Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
private String customerId;
@Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
private String title;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText;
@Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
private String assignedCustomers;
@Type(type = "json")
@Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY)
private JsonNode configuration;
@ -68,10 +80,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
if (dashboard.getTenantId() != null) {
this.tenantId = toString(dashboard.getTenantId().getId());
}
if (dashboard.getCustomerId() != null) {
this.customerId = toString(dashboard.getCustomerId().getId());
}
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();
}
@ -92,10 +108,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
if (tenantId != null) {
dashboard.setTenantId(new TenantId(toUUID(tenantId)));
}
if (customerId != null) {
dashboard.setCustomerId(new CustomerId(toUUID(customerId)));
}
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);
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;
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.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
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.TenantId;
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.Entity;
import javax.persistence.Table;
import java.io.IOException;
import java.util.HashSet;
@Data
@Slf4j
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
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)
private String tenantId;
@Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
private String customerId;
@Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
private String title;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText;
@Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
private String assignedCustomers;
public DashboardInfoEntity() {
super();
}
@ -59,10 +71,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
if (dashboardInfo.getTenantId() != null) {
this.tenantId = toString(dashboardInfo.getTenantId().getId());
}
if (dashboardInfo.getCustomerId() != null) {
this.customerId = toString(dashboardInfo.getCustomerId().getId());
}
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
@ -86,10 +102,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
if (tenantId != null) {
dashboardInfo.setTenantId(new TenantId(toUUID(tenantId)));
}
if (customerId != null) {
dashboardInfo.setCustomerId(new CustomerId(toUUID(customerId)));
}
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;
}

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.UUIDBased;
import org.thingsboard.server.common.data.page.BasePageLink;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -116,7 +117,7 @@ public class Validator {
* @param pageLink the page link
* @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)) {
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,
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;
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.data.domain.PageRequest;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
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.id.CustomerId;
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.dashboard.DashboardInfoDao;
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.util.SqlDao;
import java.sql.Time;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
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.
*/
@Slf4j
@Component
@SqlDao
public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
@Autowired
private RelationDao relationDao;
@Autowired
private DashboardInfoRepository dashboardInfoRepository;
@ -65,13 +81,17 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
}
@Override
public List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
return DaoUtil.convertDataList(dashboardInfoRepository
.findByTenantIdAndCustomerId(
UUIDConverter.fromTimeUUID(tenantId),
UUIDConverter.fromTimeUUID(customerId),
Objects.toString(pageLink.getTextSearch(), ""),
pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()),
new PageRequest(0, pageLink.getLimit())));
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);
ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink);
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);
});
}
}

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 (
id timeuuid,
tenant_id timeuuid,
customer_id timeuuid,
title text,
search_text text,
assigned_customers 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
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 ( tenant_id, search_text, id, customer_id )
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_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 );
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 );
CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf (
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 (
id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
configuration varchar(10000000),
customer_id varchar(31),
assigned_customers varchar(1000000),
search_text varchar(255),
tenant_id varchar(31),
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.page.TextPageData;
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.model.ModelConstants;
import java.io.IOException;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
@ -68,8 +72,6 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
Assert.assertNotNull(savedDashboard.getId());
Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
Assert.assertEquals(dashboard.getTenantId(), savedDashboard.getTenantId());
Assert.assertNotNull(savedDashboard.getCustomerId());
Assert.assertEquals(ModelConstants.NULL_UUID, savedDashboard.getCustomerId().getId());
Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
savedDashboard.setTitle("My new dashboard");
@ -280,7 +282,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
}
@Test
public void testFindDashboardsByTenantIdAndCustomerId() {
public void testFindDashboardsByTenantIdAndCustomerId() throws ExecutionException, InterruptedException {
Tenant tenant = new Tenant();
tenant.setTitle("Test tenant");
tenant = tenantService.saveTenant(tenant);
@ -303,10 +305,10 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
}
List<DashboardInfo> loadedDashboards = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(23);
TextPageData<DashboardInfo> pageData = null;
TimePageLink pageLink = new TimePageLink(23);
TimePageData<DashboardInfo> pageData = null;
do {
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
loadedDashboards.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
@ -318,98 +320,14 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
Assert.assertEquals(dashboards, loadedDashboards);
dashboardService.unassignCustomerDashboards(tenantId, customerId);
dashboardService.unassignCustomerDashboards(customerId);
pageLink = new TextPageLink(42);
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
pageLink = new TimePageLink(42);
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty());
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;
import com.datastax.driver.core.utils.UUIDs;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.DashboardInfo;
@ -40,53 +41,26 @@ public class JpaDashboardInfoDaoTest extends AbstractJpaDaoTest {
@Test
public void testFindDashboardsByTenantId() {
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);
createDashboard(tenantId1, i);
createDashboard(tenantId2, i * 2);
}
TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD");
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);
List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink2);
assertEquals(5, dashboardInfos2.size());
Assert.assertEquals(5, dashboardInfos2.size());
}
@Test
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) {
private void createDashboard(UUID tenantId, int index) {
DashboardInfo dashboardInfo = new DashboardInfo();
dashboardInfo.setId(new DashboardId(UUIDs.timeBased()));
dashboardInfo.setTenantId(new TenantId(tenantId));
dashboardInfo.setCustomerId(new CustomerId(customerId));
dashboardInfo.setTitle("DASHBOARD_" + index);
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;
/*@ngInject*/
function DashboardService($rootScope, $http, $q, $location, customerService) {
function DashboardService($rootScope, $http, $q, $location, $filter) {
var stDiffPromise;
@ -37,7 +37,11 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
deleteDashboard: deleteDashboard,
saveDashboard: saveDashboard,
unassignDashboardFromCustomer: unassignDashboardFromCustomer,
updateDashboardCustomers: updateDashboardCustomers,
addDashboardCustomers: addDashboardCustomers,
removeDashboardCustomers: removeDashboardCustomers,
makeDashboardPublic: makeDashboardPublic,
makeDashboardPrivate: makeDashboardPrivate,
getPublicDashboardLink: getPublicDashboardLink
}
@ -56,14 +60,14 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
url += '&textOffset=' + pageLink.textOffset;
}
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
deferred.resolve(prepareDashboards(response.data));
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getTenantDashboards(pageLink, applyCustomersInfo, config) {
function getTenantDashboards(pageLink, config) {
var deferred = $q.defer();
var url = '/api/tenant/dashboards?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@ -76,51 +80,25 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
url += '&textOffset=' + pageLink.textOffset;
}
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
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);
}
deferred.resolve(prepareDashboards(response.data));
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getCustomerDashboards(customerId, pageLink, applyCustomersInfo, config) {
function getCustomerDashboards(customerId, pageLink, config) {
var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/dashboards?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
url += '&textSearch=' + pageLink.textSearch;
}
if (angular.isDefined(pageLink.idOffset)) {
url += '&idOffset=' + pageLink.idOffset;
}
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
url += '&offset=' + pageLink.idOffset;
}
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
function success(data) {
response.data.data = data;
deferred.resolve(response.data);
},
function fail() {
deferred.reject();
}
);
} else {
deferred.resolve(response.data);
response.data = prepareDashboards(response.data);
if (pageLink.textSearch) {
response.data.data = $filter('filter')(response.data.data, {title: pageLink.textSearch});
}
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
@ -151,7 +129,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
var deferred = $q.defer();
var url = '/api/dashboard/' + dashboardId;
$http.get(url, null).then(function success(response) {
deferred.resolve(response.data);
deferred.resolve(prepareDashboard(response.data));
}, function fail() {
deferred.reject();
});
@ -162,7 +140,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
var deferred = $q.defer();
var url = '/api/dashboard/info/' + dashboardId;
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
deferred.resolve(prepareDashboard(response.data));
}, function fail() {
deferred.reject();
});
@ -172,8 +150,8 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
function saveDashboard(dashboard) {
var deferred = $q.defer();
var url = '/api/dashboard';
$http.post(url, dashboard).then(function success(response) {
deferred.resolve(response.data);
$http.post(url, cleanDashboard(dashboard)).then(function success(response) {
deferred.resolve(prepareDashboard(response.data));
}, function fail() {
deferred.reject();
});
@ -195,18 +173,51 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
$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 unassignDashboardFromCustomer(dashboardId) {
function unassignDashboardFromCustomer(customerId, dashboardId) {
var deferred = $q.defer();
var url = '/api/customer/dashboard/' + dashboardId;
var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
$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() {
deferred.reject();
});
@ -217,7 +228,18 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
var deferred = $q.defer();
var url = '/api/customer/public/dashboard/' + dashboardId;
$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() {
deferred.reject();
});
@ -230,8 +252,44 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
if (port != 80 && port != 443) {
url += ":" + port;
}
url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.customerId.id;
url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.publicCustomerId;
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;
case types.entityType.dashboard:
if (user.authority === 'CUSTOMER_USER') {
promise = dashboardService.getCustomerDashboards(customerId, pageLink, false, config);
promise = dashboardService.getCustomerDashboards(customerId, pageLink, config);
} else {
promise = dashboardService.getTenantDashboards(pageLink, false, config);
promise = dashboardService.getTenantDashboards(pageLink, config);
}
break;
case types.entityType.user:
@ -403,6 +403,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
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) {
var entityId = null;
if (stateParams) {
@ -417,6 +432,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
if (!entityId) {
entityId = filter.defaultStateEntity;
}
if (entityId) {
entityId = resolveAliasEntityId(entityId.entityType, entityId.id);
}
return entityId;
}
@ -432,7 +450,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
var stateEntityId = getStateEntityId(filter, stateParams);
switch (filter.type) {
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) {
result.entities = entitiesToEntitiesInfo([entity]);
deferred.resolve(result);
@ -530,10 +549,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
rootEntityId = filter.rootEntity.id;
}
if (rootEntityType && rootEntityId) {
var relationQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
var searchQuery = {
parameters: {
rootId: rootEntityId,
rootType: rootEntityType,
rootId: relationQueryRootEntityId.id,
rootType: relationQueryRootEntityId.entityType,
direction: filter.direction
},
filters: filter.filters
@ -571,10 +591,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
rootEntityId = filter.rootEntity.id;
}
if (rootEntityType && rootEntityId) {
var searchQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
searchQuery = {
parameters: {
rootId: rootEntityId,
rootType: rootEntityType,
rootId: searchQueryRootEntityId.id,
rootType: searchQueryRootEntityId.entityType,
direction: filter.direction
},
relationType: filter.relationType
@ -709,7 +730,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return result;
}
function prepareAllowedEntityTypesList(allowedEntityTypes) {
function prepareAllowedEntityTypesList(allowedEntityTypes, useAliasEntityTypes) {
var authority = userService.getAuthority();
var entityTypes = {};
switch(authority) {
@ -726,12 +747,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
entityTypes.rule = types.entityType.rule;
entityTypes.plugin = types.entityType.plugin;
entityTypes.dashboard = types.entityType.dashboard;
if (useAliasEntityTypes) {
entityTypes.current_customer = types.aliasEntityType.current_customer;
}
break;
case 'CUSTOMER_USER':
entityTypes.device = types.entityType.device;
entityTypes.asset = types.entityType.asset;
entityTypes.customer = types.entityType.customer;
entityTypes.dashboard = types.entityType.dashboard;
if (useAliasEntityTypes) {
entityTypes.current_customer = types.aliasEntityType.current_customer;
}
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 fetchDashboardsPromise;
if (currentUser.authority === 'TENANT_ADMIN') {
fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink, false);
fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink);
} else {
fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink, false);
fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink);
}
fetchDashboardsPromise.then(
function success(result) {

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

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

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

@ -48,7 +48,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
var promise;
if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
if (scope.customerId) {
promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false, {ignoreLoading: true});
promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, {ignoreLoading: true});
} else {
promise = $q.when({data: []});
}
@ -60,7 +60,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
promise = $q.when({data: []});
}
} 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;
if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
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 {
promise = $q.when({data: []});
}
} else {
promise = dashboardService.getTenantDashboards(pageLink, false, {ignoreLoading: true});
promise = dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true});
}
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 () {
if (vm.dashboards.hasNext && !vm.dashboards.pending) {
vm.dashboards.pending = true;
dashboardService.getTenantDashboards(vm.dashboards.nextPageLink, false).then(
dashboardService.getTenantDashboards(vm.dashboards.nextPageLink).then(
function success(dashboards) {
vm.dashboards.data = vm.dashboards.data.concat(dashboards.data);
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.
-->
<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'dashboard.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
<div class="tb-small" ng-show="vm.isPublic()">{{'dashboard.public' | translate}}</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.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'"
class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button>
<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>
<md-button ng-click="onAssignToCustomer({event: $event})"
ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer"
class="md-raised md-primary">{{ 'dashboard.assign-to-customer' | translate }}</md-button>
<md-button ng-click="onUnassignFromCustomer({event: $event, isPublic: isPublic})"
ng-show="!isEdit && (dashboardScope === 'customer' || dashboardScope === 'tenant') && isAssignedToCustomer"
class="md-raised md-primary">{{ isPublic ? 'dashboard.make-private' : 'dashboard.unassign-from-customer' | translate }}</md-button>
<md-button ng-click="onMakePrivate({event: $event})"
ng-show="!isEdit && ((dashboardScope === 'tenant' && dashboard.publicCustomerId ||
dashboardScope === 'customer' && customerId == dashboard.publicCustomerId))"
class="md-raised md-primary">{{ 'dashboard.make-private' | translate }}</md-button>
<md-button ng-click="onManageAssignedCustomers({event: $event})"
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})"
ng-show="!isEdit && dashboardScope === 'tenant'"
class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button>
<md-content class="md-padding" layout="column">
<md-input-container class="md-block"
ng-show="!isEdit && isAssignedToCustomer && !isPublic && dashboardScope === 'tenant'">
<label translate>dashboard.assignedToCustomer</label>
<input ng-model="assignedCustomer.title" disabled>
ng-show="!isEdit && dashboard.assignedCustomersText && dashboardScope === 'tenant'">
<label translate>dashboard.assignedToCustomers</label>
<input ng-model="dashboard.assignedCustomersText" disabled>
</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;"
share-title="{{ 'dashboard.socialshare-title' | 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 */
/*@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 template = $templateCache.get(dashboardFieldsetTemplate);
element.html(template);
scope.isAssignedToCustomer = false;
scope.isPublic = false;
scope.assignedCustomer = null;
scope.publicLink = null;
scope.$watch('dashboard', function(newVal) {
if (newVal) {
if (scope.dashboard.customerId && scope.dashboard.customerId.id !== types.id.nullUid) {
scope.isAssignedToCustomer = true;
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;
}
}
);
if (scope.dashboard.publicCustomerId) {
scope.publicLink = dashboardService.getPublicDashboardLink(scope.dashboard);
} else {
scope.isAssignedToCustomer = false;
scope.isPublic = false;
scope.publicLink = null;
scope.assignedCustomer = null;
}
}
});
@ -66,10 +47,12 @@ export default function DashboardDirective($compile, $templateCache, $translate,
scope: {
dashboard: '=',
isEdit: '=',
customerId: '=',
dashboardScope: '=',
theForm: '=',
onAssignToCustomer: '&',
onMakePublic: '&',
onMakePrivate: '&',
onManageAssignedCustomers: '&',
onUnassignFromCustomer: '&',
onExportDashboard: '&',
onDeleteDashboard: '&'

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

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

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

@ -25,10 +25,12 @@
<tb-dashboard-details dashboard="vm.grid.operatingItem()"
is-edit="vm.grid.detailsConfig.isDetailsEditMode"
dashboard-scope="vm.dashboardsScope"
customer-id="vm.customerId"
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-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-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details>
</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 DashboardController from './dashboard.controller';
import DashboardSettingsController from './dashboard-settings.controller';
import AssignDashboardToCustomerController from './assign-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 DashboardDirective from './dashboard.directive';
import EditWidgetDirective from './edit-widget.directive';
@ -74,8 +74,8 @@ export default angular.module('thingsboard.dashboard', [
.controller('MakeDashboardPublicDialogController', MakeDashboardPublicDialogController)
.controller('DashboardController', DashboardController)
.controller('DashboardSettingsController', DashboardSettingsController)
.controller('AssignDashboardToCustomerController', AssignDashboardToCustomerController)
.controller('AddDashboardsToCustomerController', AddDashboardsToCustomerController)
.controller('ManageAssignedCustomersController', ManageAssignedCustomersController)
.controller('AddWidgetController', AddWidgetController)
.directive('tbDashboardDetails', DashboardDirective)
.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) {
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 (scope.excludeEntityIds && scope.excludeEntityIds.length) {
var entities = [];
@ -71,7 +76,11 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
ngModelCtrl.$render = function () {
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) {
scope.entity = entity;
},
@ -114,55 +123,61 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
scope.selectEntityText = 'asset.select-asset';
scope.entityText = 'asset.asset';
scope.noEntitiesMatchingText = 'asset.no-assets-matching';
scope.entityRequiredText = 'asset.asset-required'
scope.entityRequiredText = 'asset.asset-required';
break;
case types.entityType.device:
scope.selectEntityText = 'device.select-device';
scope.entityText = 'device.device';
scope.noEntitiesMatchingText = 'device.no-devices-matching';
scope.entityRequiredText = 'device.device-required'
scope.entityRequiredText = 'device.device-required';
break;
case types.entityType.rule:
scope.selectEntityText = 'rule.select-rule';
scope.entityText = 'rule.rule';
scope.noEntitiesMatchingText = 'rule.no-rules-matching';
scope.entityRequiredText = 'rule.rule-required'
scope.entityRequiredText = 'rule.rule-required';
break;
case types.entityType.plugin:
scope.selectEntityText = 'plugin.select-plugin';
scope.entityText = 'plugin.plugin';
scope.noEntitiesMatchingText = 'plugin.no-plugins-matching';
scope.entityRequiredText = 'plugin.plugin-required'
scope.entityRequiredText = 'plugin.plugin-required';
break;
case types.entityType.tenant:
scope.selectEntityText = 'tenant.select-tenant';
scope.entityText = 'tenant.tenant';
scope.noEntitiesMatchingText = 'tenant.no-tenants-matching';
scope.entityRequiredText = 'tenant.tenant-required'
scope.entityRequiredText = 'tenant.tenant-required';
break;
case types.entityType.customer:
scope.selectEntityText = 'customer.select-customer';
scope.entityText = 'customer.customer';
scope.noEntitiesMatchingText = 'customer.no-customers-matching';
scope.entityRequiredText = 'customer.customer-required'
scope.entityRequiredText = 'customer.customer-required';
break;
case types.entityType.user:
scope.selectEntityText = 'user.select-user';
scope.entityText = 'user.user';
scope.noEntitiesMatchingText = 'user.no-users-matching';
scope.entityRequiredText = 'user.user-required'
scope.entityRequiredText = 'user.user-required';
break;
case types.entityType.dashboard:
scope.selectEntityText = 'dashboard.select-dashboard';
scope.entityText = 'dashboard.dashboard';
scope.noEntitiesMatchingText = 'dashboard.no-dashboards-matching';
scope.entityRequiredText = 'dashboard.dashboard-required'
scope.entityRequiredText = 'dashboard.dashboard-required';
break;
case types.entityType.alarm:
scope.selectEntityText = 'alarm.select-alarm';
scope.entityText = 'alarm.alarm';
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;
}
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
the-form="theForm"
tb-required="true"
use-alias-entity-types="true"
ng-model="filter.singleEntity">
</tb-entity-select>
</section>
@ -78,6 +79,7 @@
<tb-entity-select flex
the-form="theForm"
tb-required="false"
use-alias-entity-types="true"
ng-model="filter.defaultStateEntity">
</tb-entity-select>
</div>
@ -123,6 +125,7 @@
the-form="theForm"
tb-required="!filter.rootStateEntity"
ng-disabled="filter.rootStateEntity"
use-alias-entity-types="true"
ng-model="filter.rootEntity">
</tb-entity-select>
</div>
@ -139,6 +142,7 @@
<tb-entity-select flex
the-form="theForm"
tb-required="false"
use-alias-entity-types="true"
ng-model="filter.defaultStateEntity">
</tb-entity-select>
</div>
@ -182,6 +186,7 @@
the-form="theForm"
tb-required="!filter.rootStateEntity"
ng-disabled="filter.rootStateEntity"
use-alias-entity-types="true"
ng-model="filter.rootEntity">
</tb-entity-select>
</div>
@ -198,6 +203,7 @@
<tb-entity-select flex
the-form="theForm"
tb-required="false"
use-alias-entity-types="true"
ng-model="filter.defaultStateEntity">
</tb-entity-select>
</div>
@ -249,6 +255,7 @@
the-form="theForm"
tb-required="!filter.rootStateEntity"
ng-disabled="filter.rootStateEntity"
use-alias-entity-types="true"
ng-model="filter.rootEntity">
</tb-entity-select>
</div>
@ -265,6 +272,7 @@
<tb-entity-select flex
the-form="theForm"
tb-required="false"
use-alias-entity-types="true"
ng-model="filter.defaultStateEntity">
</tb-entity-select>
</div>

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

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

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

@ -20,6 +20,7 @@
the-form="theForm"
ng-disabled="disabled"
tb-required="tbRequired"
use-alias-entity-types="useAliasEntityTypes"
ng-model="model.entityType">
</tb-entity-type-select>
<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.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes, scope.useAliasEntityTypes);
scope.typeName = function(type) {
return type ? types.entityTypeTranslations[type].type : '';
@ -79,7 +79,8 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity
theForm: '=?',
tbRequired: '=?',
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) {
var name = dashboard.title;
name = name.toLowerCase().replace(/\W/g,"_");
exportToPc(prepareExport(dashboard), name + '.json');
exportToPc(prepareDashboardExport(dashboard), name + '.json');
},
function fail(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) {
var deferred = $q.defer();
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",
"select-customer": "Select customer",
"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": {
"date-from": "Date from",
@ -404,6 +407,12 @@ export default angular.module('thingsboard.locale', [])
"unassign-from-customer": "Unassign from customer",
"make-public": "Make dashboard public",
"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-widgets": "No widgets configured",
"add-widget": "Add new widget",
@ -418,7 +427,8 @@ export default angular.module('thingsboard.locale', [])
"add-dashboard-text": "Add new dashboard",
"assign-dashboards": "Assign dashboards",
"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",
"unassign-dashboards": "Unassign dashboards",
"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.",
"select-devices": "Select devices",
"assignedToCustomer": "Assigned to customer",
"assignedToCustomers": "Assigned to customers",
"public": "Public",
"public-link": "Public link",
"copy-public-link": "Copy public link",
@ -735,6 +746,7 @@ export default angular.module('thingsboard.locale', [])
"type-alarms": "Alarms",
"list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
"alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
"type-current-customer": "Current Customer",
"search": "Search entities",
"selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
"entity-name": "Entity name",

Loading…
Cancel
Save