From 063a1093e0179ae7840d0f8e3bce3afdeeb08dad Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 27 Feb 2018 18:30:23 +0200 Subject: [PATCH 1/5] Ability to assign dashboards to multiple customers - DAO Layer. --- .../main/data/upgrade/1.4.0/schema_update.cql | 23 +++ .../main/data/upgrade/1.4.0/schema_update.sql | 10 ++ .../server/controller/BaseController.java | 12 +- .../controller/DashboardController.java | 67 +++++--- .../CassandraDatabaseUpgradeService.java | 34 +++- .../service/install/DatabaseHelper.java | 93 ++++++++++ .../DefaultSystemDataLoaderService.java | 6 +- .../install/SqlDatabaseUpgradeService.java | 34 +++- .../install/cql/CassandraDbHelper.java | 5 +- .../service/install/sql/SqlDbHelper.java | 146 ++++++++++++++++ .../BaseDashboardControllerTest.java | 118 ++----------- .../server/common/data/Dashboard.java | 2 - .../server/common/data/DashboardInfo.java | 63 +++++-- .../data/relation/RelationTypeGroup.java | 3 +- .../dao/customer/CustomerServiceImpl.java | 4 +- .../dashboard/CassandraDashboardInfoDao.java | 32 +++- .../dao/dashboard/DashboardInfoDao.java | 4 +- .../dao/dashboard/DashboardService.java | 10 +- .../dao/dashboard/DashboardServiceImpl.java | 159 ++++++++++++++---- .../server/dao/model/ModelConstants.java | 4 +- .../dao/model/nosql/DashboardEntity.java | 51 +++--- .../dao/model/nosql/DashboardInfoEntity.java | 49 ++++-- .../server/dao/model/sql/DashboardEntity.java | 32 +++- .../dao/model/sql/DashboardInfoEntity.java | 33 +++- .../dao/service/TimePaginatedRemover.java | 50 ++++++ .../server/dao/service/Validator.java | 3 +- .../dashboard/DashboardInfoRepository.java | 8 - .../sql/dashboard/JpaDashboardInfoDao.java | 36 +++- dao/src/main/resources/cassandra/schema.cql | 17 +- dao/src/main/resources/sql/schema.sql | 2 +- .../dao/service/BaseDashboardServiceTest.java | 102 ++--------- .../dashboard/JpaDashboardInfoDaoTest.java | 38 +---- 32 files changed, 830 insertions(+), 420 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java create mode 100644 application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java diff --git a/application/src/main/data/upgrade/1.4.0/schema_update.cql b/application/src/main/data/upgrade/1.4.0/schema_update.cql index c5b656feef..7a63bb3fba 100644 --- a/application/src/main/data/upgrade/1.4.0/schema_update.cql +++ b/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 ); + diff --git a/application/src/main/data/upgrade/1.4.0/schema_update.sql b/application/src/main/data/upgrade/1.4.0/schema_update.sql index b2e3a9720f..c635414e52 100644 --- a/application/src/main/data/upgrade/1.4.0/schema_update.sql +++ b/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) +); diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 28489ca427..a72f2f384b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/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); @@ -435,27 +435,23 @@ public abstract class BaseController { 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.getAssignedCustomers() == null || !dashboard.getAssignedCustomers().containsKey(authUser.getCustomerId().toString())) { 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 { diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index 34320b12df..30d57f3f4d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -28,6 +28,8 @@ 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; @@ -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; @@ -192,7 +193,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 +207,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 +273,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 getCustomerDashboards( + public TimePageData 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); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java index dacf453605..e3691535bc 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java +++ b/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"); + 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); + 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); diff --git a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java new file mode 100644 index 0000000000..44f42dd570 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java @@ -0,0 +1,93 @@ +/** + * 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.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 com.fasterxml.jackson.databind.JsonNode; +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 { + String[] columns = new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION}; + try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(dashboardsDump), CSV_DUMP_FORMAT.withHeader(columns))) { + 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 customerIds = new ArrayList<>(); + if (!StringUtils.isEmpty(assignedCustomersString)) { + try { + JsonNode assignedCustomersJson = objectMapper.readTree(assignedCustomersString); + Map assignedCustomers = objectMapper.treeToValue(assignedCustomersJson, HashMap.class); + assignedCustomers.forEach((strCustomerId, title) -> { + customerIds.add(new CustomerId(UUID.fromString(strCustomerId))); + }); + } catch (IOException e) { + log.error("Unable to parse assigned customers field", e); + } + } + if (!StringUtils.isEmpty(customerIdString)) { + customerIds.add(new CustomerId(toUUID(customerIdString, sql))); + } + 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); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 4e81f94419..a102dcbc18 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/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); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 098b4fe970..e1e1e5f945 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/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"); + 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); + 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); diff --git a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java b/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java index 0a411f65a9..ef4610eaf1 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java +++ b/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java @@ -17,7 +17,6 @@ package org.thingsboard.server.service.install.cql; import com.datastax.driver.core.*; -import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVPrinter; import org.apache.commons.csv.CSVRecord; @@ -28,9 +27,9 @@ 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 { diff --git a/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java b/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java new file mode 100644 index 0000000000..fa5175fe89 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java @@ -0,0 +1,146 @@ +/** + * 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.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 { + + DatabaseMetaData metaData = conn.getMetaData(); + ResultSet res = metaData.getTables(null, null, tableName, + new String[] {"TABLE"}); + if (res.next()) { + res.close(); + Path dumpFile = Files.createTempFile(dumpPrefix, null); + Files.deleteIfExists(dumpFile); + try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), CSV_DUMP_FORMAT)) { + try (PreparedStatement stmt = conn.prepareStatement("SELECT * FROM " + tableName)) { + try (ResultSet tableRes = stmt.executeQuery()) { + ResultSetMetaData resMetaData = tableRes.getMetaData(); + Map columnIndexMap = new HashMap<>(); + for (int i = 0; i < resMetaData.getColumnCount(); i++) { + String columnName = resMetaData.getColumnName(i); + columnIndexMap.put(columnName, i); + } + while(tableRes.next()) { + dumpRow(tableRes, columnIndexMap, columns, defaultValues, csvPrinter); + } + } + } + } + return dumpFile; + } else { + return null; + } + } + + public static void loadTable(Connection conn, String tableName, String[] columns, Path sourceFile) throws Exception { + PreparedStatement prepared = conn.prepareStatement(createInsertStatement(tableName, columns)); + prepared.getParameterMetaData(); + try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), CSV_DUMP_FORMAT.withHeader(columns))) { + csvParser.forEach(record -> { + try { + for (int i=0;i columnIndexMap, String[] columns, + String[] defaultValues, CSVPrinter csvPrinter) throws Exception { + List record = new ArrayList<>(); + for (int i=0;i columnIndexMap, ResultSet res) { + int index = columnIndexMap.containsKey(column) ? columnIndexMap.get(column) : -1; + if (index > -1) { + String str; + try { + Object obj = res.getObject(index); + if (obj == null) { + str = ""; + } 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(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java index bb064116f1..434862bd9d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java +++ b/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().containsKey(savedCustomer.getId().toString())); + Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); - Assert.assertEquals(savedCustomer.getId(), foundDashboard.getCustomerId()); + Assert.assertTrue(foundDashboard.getAssignedCustomers().containsKey(savedCustomer.getId().toString())); 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 loadedDashboards = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(21); - TextPageData pageData = null; + TimePageLink pageLink = new TimePageLink(21); + TimePageData pageData = null; do { - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", - new TypeReference>(){}, pageLink); + pageData = doGetTypedWithTimePageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", + new TypeReference>(){}, 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 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 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 loadedDashboardsTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(18, title1); - TextPageData pageData = null; - do { - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", - new TypeReference>(){}, 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 loadedDashboardsTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(7, title2); - do { - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", - new TypeReference>(){}, 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>(){}, 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>(){}, pageLink); - Assert.assertFalse(pageData.hasNext()); - Assert.assertEquals(0, pageData.getData().size()); - } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java index 71f2b1308b..fa9f048e2a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java +++ b/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="); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java index 15898c6c63..7e18dc6b80 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java @@ -20,11 +20,16 @@ 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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + public class DashboardInfo extends SearchTextBased implements HasName { private TenantId tenantId; - private CustomerId customerId; private String title; + private Map assignedCustomers; public DashboardInfo() { super(); @@ -37,8 +42,8 @@ public class DashboardInfo extends SearchTextBased 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 +54,6 @@ public class DashboardInfo extends SearchTextBased 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 +62,44 @@ public class DashboardInfo extends SearchTextBased implements HasNa this.title = title; } + public Map getAssignedCustomers() { + return assignedCustomers; + } + + public void setAssignedCustomers(Map assignedCustomers) { + this.assignedCustomers = assignedCustomers; + } + + public boolean addAssignedCustomer(CustomerId customerId, String title) { + if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) { + return false; + } else { + if (this.assignedCustomers == null) { + this.assignedCustomers = new HashMap<>(); + } + this.assignedCustomers.put(customerId.toString(), title); + return true; + } + } + + public boolean updateAssignedCustomer(CustomerId customerId, String title) { + if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) { + this.assignedCustomers.put(customerId.toString(), title); + return true; + } else { + return false; + } + } + + public boolean removeAssignedCustomer(CustomerId customerId) { + if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) { + this.assignedCustomers.remove(customerId.toString()); + return true; + } else { + return false; + } + } + @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getName() { @@ -80,7 +115,6 @@ public class DashboardInfo extends SearchTextBased 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 +129,6 @@ public class DashboardInfo extends SearchTextBased 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 +147,6 @@ public class DashboardInfo extends SearchTextBased 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("]"); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java index 82798ab792..7c9c5e62fd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java +++ b/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 } diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java index f76d654bf9..f118aff7a2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java +++ b/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.getTenantId(), savedCustomer.getId(), savedCustomer.getTitle()); + return savedCustomer; } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java index d0651a47c3..5f35913088 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java +++ b/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 implements DashboardInfoDao { + @Autowired + private RelationDao relationDao; + @Override protected Class getColumnFamilyClass() { return DashboardInfoEntity.class; @@ -59,15 +72,18 @@ public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) { + public ListenableFuture> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) { log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink); - List 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> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink); + + return Futures.transform(relations, (AsyncFunction, List>) input -> { + List> dashboardFutures = new ArrayList<>(input.size()); + for (EntityRelation relation : input) { + dashboardFutures.add(findByIdAsync(relation.getTo().getId())); + } + return Futures.successfulAsList(dashboardFutures); + }); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java index a26bd1410f..baa020f95f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java +++ b/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 { * @param pageLink the page link * @return the list of dashboard objects */ - List findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink); + ListenableFuture> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java index 74d85445f8..f4af29c88d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java +++ b/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.sql.Time; 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 findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); + ListenableFuture> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink); void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId); + void updateCustomerDashboards(TenantId tenantId, CustomerId customerId, String customerTitle); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index 53fdb731dc..b9fa3abdc9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/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,59 @@ 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(customerId, customer.getTitle())) { + 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); + if (dashboard.removeAssignedCustomer(customerId)) { + 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 updateAssignedCustomerTitle(DashboardId dashboardId, CustomerId customerId, String customerTitle) { + Dashboard dashboard = findDashboardById(dashboardId); + if (dashboard.updateAssignedCustomer(customerId, customerTitle)) { + 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,13 +190,20 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } @Override - public TextPageData findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) { + public ListenableFuture> 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 dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); - return new TextPageData<>(dashboards, pageLink); + ListenableFuture> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); + + return Futures.transform(dashboards, new Function, TimePageData>() { + @Nullable + @Override + public TimePageData apply(@Nullable List dashboards) { + return new TimePageData<>(dashboards, pageLink); + } + }); } @Override @@ -148,9 +211,18 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb log.trace("Executing unassignCustomerDashboards, tenantId [{}], customerId [{}]", tenantId, customerId); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); Validator.validateId(customerId, "Incorrect customerId " + customerId); - new CustomerDashboardsUnassigner(tenantId).removeEntities(customerId); + new CustomerDashboardsUnassigner(tenantId, customerId).removeEntities(customerId); } - + + @Override + public void updateCustomerDashboards(TenantId tenantId, CustomerId customerId, String customerTitle) { + log.trace("Executing updateCustomerDashboards, tenantId [{}], customerId [{}], customerTitle [{}]", tenantId, customerId, customerTitle); + Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + Validator.validateId(customerId, "Incorrect customerId " + customerId); + Validator.validateString(customerTitle, "Incorrect customerTitle " + customerTitle); + new CustomerDashboardsUpdater(tenantId, customerId, customerTitle).removeEntities(customerId); + } + private DataValidator dashboardValidator = new DataValidator() { @Override @@ -166,17 +238,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 +255,60 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } }; - private class CustomerDashboardsUnassigner extends PaginatedRemover { + private class CustomerDashboardsUnassigner extends TimePaginatedRemover { private TenantId tenantId; + private CustomerId customerId; - CustomerDashboardsUnassigner(TenantId tenantId) { + CustomerDashboardsUnassigner(TenantId tenantId, CustomerId customerId) { this.tenantId = tenantId; + this.customerId = customerId; } @Override - protected List findEntities(CustomerId id, TextPageLink pageLink) { - return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink); + protected List findEntities(CustomerId id, TimePageLink pageLink) { + try { + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink).get(); + } catch (InterruptedException | ExecutionException e) { + log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", tenantId, id); + throw new RuntimeException(e); + } } @Override protected void removeEntity(DashboardInfo entity) { - unassignDashboardFromCustomer(new DashboardId(entity.getUuidId())); + unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()), this.customerId); } } + private class CustomerDashboardsUpdater extends TimePaginatedRemover { + + private TenantId tenantId; + private CustomerId customerId; + private String customerTitle; + + CustomerDashboardsUpdater(TenantId tenantId, CustomerId customerId, String customerTitle) { + this.tenantId = tenantId; + this.customerId = customerId; + this.customerTitle = customerTitle; + } + + @Override + protected List findEntities(CustomerId id, TimePageLink pageLink) { + try { + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink).get(); + } catch (InterruptedException | ExecutionException e) { + log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", tenantId, id); + throw new RuntimeException(e); + } + } + + @Override + protected void removeEntity(DashboardInfo entity) { + updateAssignedCustomerTitle(new DashboardId(entity.getUuidId()), this.customerId, this.customerTitle); + } + + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 66e03b0629..c275ad0f6c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/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. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java index 047a8f269e..8590c2a36f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java @@ -19,16 +19,19 @@ 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.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.EqualsAndHashCode; import lombok.ToString; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.Dashboard; -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.dao.model.SearchTextEntity; import org.thingsboard.server.dao.model.type.JsonCodec; +import java.util.HashMap; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.*; @@ -36,8 +39,11 @@ import static org.thingsboard.server.dao.model.ModelConstants.*; @Table(name = DASHBOARD_COLUMN_FAMILY_NAME) @EqualsAndHashCode @ToString +@Slf4j public final class DashboardEntity implements SearchTextEntity { - + + private static final ObjectMapper objectMapper = new ObjectMapper(); + @PartitionKey(value = 0) @Column(name = ID_PROPERTY) private UUID id; @@ -46,16 +52,15 @@ public final class DashboardEntity implements SearchTextEntity { @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, codec = JsonCodec.class) + private JsonNode assignedCustomers; + @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class) private JsonNode configuration; @@ -70,10 +75,10 @@ public final class DashboardEntity implements SearchTextEntity { 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) { + this.assignedCustomers = objectMapper.valueToTree(dashboard.getAssignedCustomers()); + } this.configuration = dashboard.getConfiguration(); } @@ -93,14 +98,6 @@ public final class DashboardEntity implements SearchTextEntity { this.tenantId = tenantId; } - public UUID getCustomerId() { - return customerId; - } - - public void setCustomerId(UUID customerId) { - this.customerId = customerId; - } - public String getTitle() { return title; } @@ -109,6 +106,14 @@ public final class DashboardEntity implements SearchTextEntity { this.title = title; } + public JsonNode getAssignedCustomers() { + return assignedCustomers; + } + + public void setAssignedCustomers(JsonNode assignedCustomers) { + this.assignedCustomers = assignedCustomers; + } + public JsonNode getConfiguration() { return configuration; } @@ -138,10 +143,14 @@ public final class DashboardEntity implements SearchTextEntity { if (tenantId != null) { dashboard.setTenantId(new TenantId(tenantId)); } - if (customerId != null) { - dashboard.setCustomerId(new CustomerId(customerId)); - } dashboard.setTitle(title); + if (assignedCustomers != null) { + try { + dashboard.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class)); + } catch (JsonProcessingException e) { + log.warn("Unable to parse assigned customers!", e); + } + } dashboard.setConfiguration(configuration); return dashboard; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java index a2a5280a30..609f3bb598 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java +++ b/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.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.EqualsAndHashCode; import lombok.ToString; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.DashboardInfo; 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.dao.model.SearchTextEntity; +import org.thingsboard.server.dao.model.type.JsonCodec; +import java.util.HashMap; +import java.util.Set; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.*; @@ -34,8 +41,11 @@ import static org.thingsboard.server.dao.model.ModelConstants.*; @Table(name = DASHBOARD_COLUMN_FAMILY_NAME) @EqualsAndHashCode @ToString +@Slf4j public class DashboardInfoEntity implements SearchTextEntity { + private static final ObjectMapper objectMapper = new ObjectMapper(); + @PartitionKey(value = 0) @Column(name = ID_PROPERTY) private UUID id; @@ -44,16 +54,15 @@ public class DashboardInfoEntity implements SearchTextEntity { @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, codec = JsonCodec.class) + private JsonNode assignedCustomers; + public DashboardInfoEntity() { super(); } @@ -65,10 +74,10 @@ public class DashboardInfoEntity implements SearchTextEntity { 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) { + this.assignedCustomers = objectMapper.valueToTree(dashboardInfo.getAssignedCustomers()); + } } public UUID getId() { @@ -87,14 +96,6 @@ public class DashboardInfoEntity implements SearchTextEntity { this.tenantId = tenantId; } - public UUID getCustomerId() { - return customerId; - } - - public void setCustomerId(UUID customerId) { - this.customerId = customerId; - } - public String getTitle() { return title; } @@ -103,6 +104,14 @@ public class DashboardInfoEntity implements SearchTextEntity { this.title = title; } + public JsonNode getAssignedCustomers() { + return assignedCustomers; + } + + public void setAssignedCustomers(JsonNode assignedCustomers) { + this.assignedCustomers = assignedCustomers; + } + @Override public String getSearchTextSource() { return getTitle(); @@ -124,10 +133,14 @@ public class DashboardInfoEntity implements SearchTextEntity { if (tenantId != null) { dashboardInfo.setTenantId(new TenantId(tenantId)); } - if (customerId != null) { - dashboardInfo.setCustomerId(new CustomerId(customerId)); - } dashboardInfo.setTitle(title); + if (assignedCustomers != null) { + try { + dashboardInfo.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class)); + } catch (JsonProcessingException e) { + log.warn("Unable to parse assigned customers!", e); + } + } return dashboardInfo; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java index bd0e0dc39f..6f7810bf55 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java @@ -16,9 +16,12 @@ 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.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.thingsboard.server.common.data.Dashboard; @@ -33,26 +36,33 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; +import java.util.HashMap; +import java.util.List; +import java.util.Set; @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 implements SearchTextEntity { + private static final ObjectMapper objectMapper = new ObjectMapper(); + @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; + @Type(type = "json") + @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY) + private JsonNode assignedCustomers; + @Type(type = "json") @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY) private JsonNode configuration; @@ -68,10 +78,10 @@ public final class DashboardEntity extends BaseSqlEntity 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) { + this.assignedCustomers = objectMapper.valueToTree(dashboard.getAssignedCustomers()); + } this.configuration = dashboard.getConfiguration(); } @@ -92,10 +102,14 @@ public final class DashboardEntity extends BaseSqlEntity implements S if (tenantId != null) { dashboard.setTenantId(new TenantId(toUUID(tenantId))); } - if (customerId != null) { - dashboard.setCustomerId(new CustomerId(toUUID(customerId))); - } dashboard.setTitle(title); + if (assignedCustomers != null) { + try { + dashboard.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class)); + } catch (JsonProcessingException e) { + log.warn("Unable to parse assigned customers!", e); + } + } dashboard.setConfiguration(configuration); return dashboard; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java index c87e97d2e3..d9b8efe8f4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java @@ -16,8 +16,13 @@ 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.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.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; @@ -29,25 +34,31 @@ import org.thingsboard.server.dao.model.SearchTextEntity; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; +import java.util.HashMap; +import java.util.Set; @Data +@Slf4j @EqualsAndHashCode(callSuper = true) @Entity @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME) public class DashboardInfoEntity extends BaseSqlEntity implements SearchTextEntity { + private static final ObjectMapper objectMapper = new ObjectMapper(); + @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; + @Type(type = "json") + @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY) + private JsonNode assignedCustomers; + public DashboardInfoEntity() { super(); } @@ -59,10 +70,10 @@ public class DashboardInfoEntity extends BaseSqlEntity 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) { + this.assignedCustomers = objectMapper.valueToTree(dashboardInfo.getAssignedCustomers()); + } } @Override @@ -86,10 +97,14 @@ public class DashboardInfoEntity extends BaseSqlEntity implements if (tenantId != null) { dashboardInfo.setTenantId(new TenantId(toUUID(tenantId))); } - if (customerId != null) { - dashboardInfo.setCustomerId(new CustomerId(toUUID(customerId))); - } dashboardInfo.setTitle(title); + if (assignedCustomers != null) { + try { + dashboardInfo.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class)); + } catch (JsonProcessingException e) { + log.warn("Unable to parse assigned customers!", e); + } + } return dashboardInfo; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java b/dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java new file mode 100644 index 0000000000..b52f7475b2 --- /dev/null +++ b/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> { + + private static final int DEFAULT_LIMIT = 100; + + public void removeEntities(I id) { + TimePageLink pageLink = new TimePageLink(DEFAULT_LIMIT); + boolean hasNext = true; + while (hasNext) { + List 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 findEntities(I id, TimePageLink pageLink); + + protected abstract void removeEntity(D entity); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java index d8a511d9c7..d962977d98 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java +++ b/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); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java index 7f0ec7abf6..ef7d0a09b3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java @@ -39,12 +39,4 @@ public interface DashboardInfoRepository extends CrudRepository :idOffset ORDER BY di.id") - List findByTenantIdAndCustomerId(@Param("tenantId") String tenantId, - @Param("customerId") String customerId, - @Param("searchText") String searchText, - @Param("idOffset") String idOffset, - Pageable pageable); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java index 204189af11..1e343a48f8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java +++ b/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 implements DashboardInfoDao { + @Autowired + private RelationDao relationDao; + @Autowired private DashboardInfoRepository dashboardInfoRepository; @@ -65,13 +81,17 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao 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> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) { + log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink); + + ListenableFuture> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink); + + return Futures.transform(relations, (AsyncFunction, List>) input -> { + List> dashboardFutures = new ArrayList<>(input.size()); + for (EntityRelation relation : input) { + dashboardFutures.add(findByIdAsync(relation.getTo().getId())); + } + return Futures.successfulAsList(dashboardFutures); + }); } } diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql index bc7884dfbe..dc83045eb4 100644 --- a/dao/src/main/resources/cassandra/schema.cql +++ b/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) diff --git a/dao/src/main/resources/sql/schema.sql b/dao/src/main/resources/sql/schema.sql index 1f739f8ea3..53ec2235e2 100644 --- a/dao/src/main/resources/sql/schema.sql +++ b/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) diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java index f0ed0b3474..0427fb44f7 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java +++ b/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 loadedDashboards = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(23); - TextPageData pageData = null; + TimePageLink pageLink = new TimePageLink(23); + TimePageData 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(); @@ -320,96 +322,12 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { dashboardService.unassignCustomerDashboards(tenantId, 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 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 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 loadedDashboardsTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(24, title1); - TextPageData 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 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); - } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java index 39bb084c33..3648cceefc 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java +++ b/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 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 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 dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink1); - assertEquals(15, dashboardInfos1.size()); - - TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null); - List 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); } From 99444395393f0f2966e94de9a67017ee6be742f9 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 27 Feb 2018 19:15:20 +0200 Subject: [PATCH 2/5] Improve CSV data dump. --- .../CassandraDatabaseUpgradeService.java | 4 +- .../install/SqlDatabaseUpgradeService.java | 4 +- .../install/cql/CassandraDbHelper.java | 24 +++++++++- .../service/install/sql/SqlDbHelper.java | 47 ++++++++++++++----- 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java index e3691535bc..0b2c808c4f 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java @@ -169,7 +169,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { 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"); + "tb-dashboards", true); log.info("Dashboards dumped."); @@ -181,7 +181,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { log.info("Restoring dashboards ..."); if (dashboardsDump != null) { CassandraDbHelper.loadCf(ks, cluster.getSession(), DASHBOARD, - new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump); + new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump, true); DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, false); Files.deleteIfExists(dashboardsDump); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index e1e1e5f945..3d73ffce47 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -79,7 +79,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { Path dashboardsDump = SqlDbHelper.dumpTableIfExists(conn, DASHBOARD, new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION}, new String[]{"", "", "", "", "", "", ""}, - "tb-dashboards"); + "tb-dashboards", true); log.info("Dashboards dumped."); log.info("Updating schema ..."); @@ -91,7 +91,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { log.info("Restoring dashboards ..."); if (dashboardsDump != null) { SqlDbHelper.loadTable(conn, DASHBOARD, - new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump); + new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump, true); DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, true); Files.deleteIfExists(dashboardsDump); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java b/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java index ef4610eaf1..dae9bb6126 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java +++ b/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.install.cql; import com.datastax.driver.core.*; +import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVPrinter; import org.apache.commons.csv.CSVRecord; @@ -33,10 +34,19 @@ 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); @@ -74,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) { diff --git a/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java b/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java index fa5175fe89..c78ceda02d 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java +++ b/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java @@ -16,6 +16,7 @@ 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; @@ -38,6 +39,11 @@ 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 { DatabaseMetaData metaData = conn.getMetaData(); ResultSet res = metaData.getTables(null, null, tableName, @@ -46,7 +52,11 @@ public class SqlDbHelper { res.close(); 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)) { try (PreparedStatement stmt = conn.prepareStatement("SELECT * FROM " + tableName)) { try (ResultSet tableRes = stmt.executeQuery()) { ResultSetMetaData resMetaData = tableRes.getMetaData(); @@ -68,19 +78,30 @@ public class SqlDbHelper { } public static void loadTable(Connection conn, String tableName, String[] columns, Path sourceFile) throws Exception { - PreparedStatement prepared = conn.prepareStatement(createInsertStatement(tableName, columns)); - prepared.getParameterMetaData(); - try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), CSV_DUMP_FORMAT.withHeader(columns))) { - csvParser.forEach(record -> { - try { - for (int i=0;i { + 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); } - prepared.execute(); - } catch (SQLException e) { - log.error("Unable to load table record!", e); - } - }); + }); + } } } From 7645e6f26ecc8e341194fc155c05026970abdcaa Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 27 Feb 2018 19:32:36 +0200 Subject: [PATCH 3/5] Fix Dashboards dump parsing --- .../server/service/install/DatabaseHelper.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java index 44f42dd570..d26565f0b5 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java @@ -55,7 +55,7 @@ public class DatabaseHelper { public static void upgradeTo40_assignDashboards(Path dashboardsDump, DashboardService dashboardService, boolean sql) throws Exception { String[] columns = new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION}; - try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(dashboardsDump), CSV_DUMP_FORMAT.withHeader(columns))) { + 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); @@ -66,14 +66,20 @@ public class DatabaseHelper { JsonNode assignedCustomersJson = objectMapper.readTree(assignedCustomersString); Map assignedCustomers = objectMapper.treeToValue(assignedCustomersJson, HashMap.class); assignedCustomers.forEach((strCustomerId, title) -> { - customerIds.add(new CustomerId(UUID.fromString(strCustomerId))); + CustomerId customerId = new CustomerId(UUID.fromString(strCustomerId)); + if (!customerId.isNullUid()) { + customerIds.add(new CustomerId(UUID.fromString(strCustomerId))); + } }); } catch (IOException e) { log.error("Unable to parse assigned customers field", e); } } if (!StringUtils.isEmpty(customerIdString)) { - customerIds.add(new CustomerId(toUUID(customerIdString, sql))); + CustomerId customerId = new CustomerId(toUUID(customerIdString, sql)); + if (!customerId.isNullUid()) { + customerIds.add(new CustomerId(toUUID(customerIdString, sql))); + } } for (CustomerId customerId : customerIds) { dashboardService.assignDashboardToCustomer(dashboardId, customerId); From 0021f007f1de05bba8e202ebc7c3c3ed0ee4aaa7 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 1 Mar 2018 11:52:52 +0200 Subject: [PATCH 4/5] Implement multiple customer assigned dashboards UI. Add Current Customer entity type for dashboard aliases. --- .../server/controller/BaseController.java | 3 +- .../controller/DashboardController.java | 160 ++++++++++++- .../service/install/DatabaseHelper.java | 17 +- .../BaseDashboardControllerTest.java | 4 +- .../server/common/data/Customer.java | 5 + .../server/common/data/DashboardInfo.java | 49 ++-- .../server/common/data/ShortCustomerInfo.java | 54 +++++ .../dao/customer/CustomerServiceImpl.java | 4 +- .../dao/dashboard/DashboardService.java | 6 +- .../dao/dashboard/DashboardServiceImpl.java | 75 +++--- .../dao/model/nosql/DashboardEntity.java | 28 ++- .../dao/model/nosql/DashboardInfoEntity.java | 32 ++- .../server/dao/model/sql/DashboardEntity.java | 26 +- .../dao/model/sql/DashboardInfoEntity.java | 27 ++- .../dao/service/BaseDashboardServiceTest.java | 2 +- ui/src/app/api/dashboard.service.js | 150 ++++++++---- ui/src/app/api/entity.service.js | 43 +++- ui/src/app/api/user.service.js | 4 +- ui/src/app/common/types.constant.js | 7 + .../dashboard-autocomplete.directive.js | 4 +- .../components/dashboard-select.directive.js | 4 +- .../add-dashboards-to-customer.controller.js | 2 +- .../assign-to-customer.controller.js | 123 ---------- .../app/dashboard/assign-to-customer.tpl.html | 76 ------ ui/src/app/dashboard/dashboard-card.scss | 27 +++ ui/src/app/dashboard/dashboard-card.tpl.html | 4 +- .../app/dashboard/dashboard-fieldset.tpl.html | 27 ++- ui/src/app/dashboard/dashboard.directive.js | 29 +-- ui/src/app/dashboard/dashboards.controller.js | 226 ++++++++++-------- ui/src/app/dashboard/dashboards.tpl.html | 6 +- ui/src/app/dashboard/index.js | 4 +- .../manage-assigned-customers.controller.js | 69 ++++++ .../manage-assigned-customers.tpl.html | 51 ++++ .../entity/entity-autocomplete.directive.js | 37 ++- ui/src/app/entity/entity-filter.tpl.html | 8 + ui/src/app/entity/entity-select.directive.js | 3 +- ui/src/app/entity/entity-select.tpl.html | 1 + .../entity/entity-type-select.directive.js | 5 +- .../import-export/import-export.service.js | 11 +- ui/src/app/locale/locale.constant.js | 16 +- 40 files changed, 890 insertions(+), 539 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java delete mode 100644 ui/src/app/dashboard/assign-to-customer.controller.js delete mode 100644 ui/src/app/dashboard/assign-to-customer.tpl.html create mode 100644 ui/src/app/dashboard/dashboard-card.scss create mode 100644 ui/src/app/dashboard/manage-assigned-customers.controller.js create mode 100644 ui/src/app/dashboard/manage-assigned-customers.tpl.html diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index a72f2f384b..c78ad18ded 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -434,7 +434,6 @@ public abstract class BaseController { try { validateId(dashboardId, "Incorrect dashboardId " + dashboardId); DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId); - SecurityUser authUser = getCurrentUser(); checkDashboard(dashboardInfo); return dashboardInfo; } catch (Exception e) { @@ -447,7 +446,7 @@ public abstract class BaseController { checkTenantId(dashboard.getTenantId()); SecurityUser authUser = getCurrentUser(); if (authUser.getAuthority() == Authority.CUSTOMER_USER) { - if (dashboard.getAssignedCustomers() == null || !dashboard.getAssignedCustomers().containsKey(authUser.getCustomerId().toString())) { + if (!dashboard.isAssignedToCustomer(authUser.getCustomerId())) { throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, ThingsboardErrorCode.PERMISSION_DENIED); } diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index 30d57f3f4d..a4664a5a14 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -18,10 +18,7 @@ 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; @@ -34,6 +31,9 @@ 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 { @@ -181,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 customerIds = new HashSet<>(); + if (strCustomerIds != null) { + for (String strCustomerId : strCustomerIds) { + customerIds.add(new CustomerId(toUUID(strCustomerId))); + } + } + + Set addedCustomerIds = new HashSet<>(); + Set removedCustomerIds = new HashSet<>(); + for (CustomerId customerId : customerIds) { + if (!dashboard.isAssignedToCustomer(customerId)) { + addedCustomerIds.add(customerId); + } + } + + Set 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 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 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 diff --git a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java index d26565f0b5..fa0dd2f4f1 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java @@ -15,12 +15,13 @@ */ 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 com.fasterxml.jackson.databind.JsonNode; +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; @@ -54,7 +55,8 @@ public class DatabaseHelper { public static final ObjectMapper objectMapper = new ObjectMapper(); public static void upgradeTo40_assignDashboards(Path dashboardsDump, DashboardService dashboardService, boolean sql) throws Exception { - String[] columns = new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION}; + 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); @@ -63,12 +65,11 @@ public class DatabaseHelper { List customerIds = new ArrayList<>(); if (!StringUtils.isEmpty(assignedCustomersString)) { try { - JsonNode assignedCustomersJson = objectMapper.readTree(assignedCustomersString); - Map assignedCustomers = objectMapper.treeToValue(assignedCustomersJson, HashMap.class); - assignedCustomers.forEach((strCustomerId, title) -> { - CustomerId customerId = new CustomerId(UUID.fromString(strCustomerId)); + Set assignedCustomers = objectMapper.readValue(assignedCustomersString, assignedCustomersType); + assignedCustomers.forEach((customerInfo) -> { + CustomerId customerId = customerInfo.getCustomerId(); if (!customerId.isNullUid()) { - customerIds.add(new CustomerId(UUID.fromString(strCustomerId))); + customerIds.add(customerId); } }); } catch (IOException e) { @@ -78,7 +79,7 @@ public class DatabaseHelper { if (!StringUtils.isEmpty(customerIdString)) { CustomerId customerId = new CustomerId(toUUID(customerIdString, sql)); if (!customerId.isNullUid()) { - customerIds.add(new CustomerId(toUUID(customerIdString, sql))); + customerIds.add(customerId); } } for (CustomerId customerId : customerIds) { diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java index 434862bd9d..d73e7bab0c 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java @@ -138,10 +138,10 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); - Assert.assertTrue(assignedDashboard.getAssignedCustomers().containsKey(savedCustomer.getId().toString())); + Assert.assertTrue(assignedDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo())); Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); - Assert.assertTrue(foundDashboard.getAssignedCustomers().containsKey(savedCustomer.getId().toString())); + Assert.assertTrue(foundDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo())); Dashboard unassignedDashboard = doDelete("/api/customer/"+savedCustomer.getId().getId().toString()+"/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java index 1ee9cab68e..59431f3381 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java @@ -69,6 +69,11 @@ public class Customer extends ContactBased implements HasName { return false; } + @JsonIgnore + public ShortCustomerInfo toShortCustomerInfo() { + return new ShortCustomerInfo(id, title, isPublic()); + } + @Override @JsonProperty(access = Access.READ_ONLY) public String getName() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java index 7e18dc6b80..65a467dbfd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java @@ -20,16 +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.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; public class DashboardInfo extends SearchTextBased implements HasName { private TenantId tenantId; private String title; - private Map assignedCustomers; + private Set assignedCustomers; public DashboardInfo() { super(); @@ -62,38 +59,56 @@ public class DashboardInfo extends SearchTextBased implements HasNa this.title = title; } - public Map getAssignedCustomers() { + public Set getAssignedCustomers() { return assignedCustomers; } - public void setAssignedCustomers(Map assignedCustomers) { + public void setAssignedCustomers(Set assignedCustomers) { this.assignedCustomers = assignedCustomers; } - public boolean addAssignedCustomer(CustomerId customerId, String title) { - if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) { + 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 HashMap<>(); + this.assignedCustomers = new HashSet<>(); } - this.assignedCustomers.put(customerId.toString(), title); + this.assignedCustomers.add(customerInfo); return true; } } - public boolean updateAssignedCustomer(CustomerId customerId, String title) { - if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) { - this.assignedCustomers.put(customerId.toString(), title); + 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(CustomerId customerId) { - if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) { - this.assignedCustomers.remove(customerId.toString()); + 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; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java new file mode 100644 index 0000000000..68ca46ab99 --- /dev/null +++ b/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(); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java index f118aff7a2..657bfba3d1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java @@ -98,7 +98,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom log.trace("Executing saveCustomer [{}]", customer); customerValidator.validate(customer); Customer savedCustomer = customerDao.save(customer); - dashboardService.updateCustomerDashboards(savedCustomer.getTenantId(), savedCustomer.getId(), savedCustomer.getTitle()); + dashboardService.updateCustomerDashboards(savedCustomer.getId()); return savedCustomer; } @@ -110,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); diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java index f4af29c88d..06426e71a3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java @@ -26,7 +26,7 @@ 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.sql.Time; +import java.util.Set; public interface DashboardService { @@ -52,8 +52,8 @@ public interface DashboardService { ListenableFuture> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink); - void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId); + void unassignCustomerDashboards(CustomerId customerId); - void updateCustomerDashboards(TenantId tenantId, CustomerId customerId, String customerTitle); + void updateCustomerDashboards(CustomerId customerId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index b9fa3abdc9..741d313940 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java @@ -117,7 +117,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) { throw new DataValidationException("Can't assign dashboard to customer from different tenant!"); } - if (dashboard.addAssignedCustomer(customerId, customer.getTitle())) { + if (dashboard.addAssignedCustomer(customer)) { try { createRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD)); } catch (ExecutionException | InterruptedException e) { @@ -133,7 +133,11 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @Override public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId) { Dashboard dashboard = findDashboardById(dashboardId); - if (dashboard.removeAssignedCustomer(customerId)) { + 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) { @@ -146,9 +150,9 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } } - private Dashboard updateAssignedCustomerTitle(DashboardId dashboardId, CustomerId customerId, String customerTitle) { + private Dashboard updateAssignedCustomer(DashboardId dashboardId, Customer customer) { Dashboard dashboard = findDashboardById(dashboardId); - if (dashboard.updateAssignedCustomer(customerId, customerTitle)) { + if (dashboard.updateAssignedCustomer(customer)) { return saveDashboard(dashboard); } else { return dashboard; @@ -207,20 +211,25 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } @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, customerId).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(TenantId tenantId, CustomerId customerId, String customerTitle) { - log.trace("Executing updateCustomerDashboards, tenantId [{}], customerId [{}], customerTitle [{}]", tenantId, customerId, customerTitle); - Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + public void updateCustomerDashboards(CustomerId customerId) { + log.trace("Executing updateCustomerDashboards, customerId [{}]", customerId); Validator.validateId(customerId, "Incorrect customerId " + customerId); - Validator.validateString(customerTitle, "Incorrect customerTitle " + customerTitle); - new CustomerDashboardsUpdater(tenantId, customerId, customerTitle).removeEntities(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 dashboardValidator = @@ -255,58 +264,52 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } }; - private class CustomerDashboardsUnassigner extends TimePaginatedRemover { - - private TenantId tenantId; - private CustomerId customerId; + private class CustomerDashboardsUnassigner extends TimePaginatedRemover { - CustomerDashboardsUnassigner(TenantId tenantId, CustomerId customerId) { - this.tenantId = tenantId; - this.customerId = customerId; + private Customer customer; + + CustomerDashboardsUnassigner(Customer customer) { + this.customer = customer; } @Override - protected List findEntities(CustomerId id, TimePageLink pageLink) { + protected List findEntities(Customer customer, TimePageLink pageLink) { try { - return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink).get(); + 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 [{}].", tenantId, id); + 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()), this.customerId); + unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()), this.customer.getId()); } } - private class CustomerDashboardsUpdater extends TimePaginatedRemover { + private class CustomerDashboardsUpdater extends TimePaginatedRemover { - private TenantId tenantId; - private CustomerId customerId; - private String customerTitle; + private Customer customer; - CustomerDashboardsUpdater(TenantId tenantId, CustomerId customerId, String customerTitle) { - this.tenantId = tenantId; - this.customerId = customerId; - this.customerTitle = customerTitle; + CustomerDashboardsUpdater(Customer customer) { + this.customer = customer; } @Override - protected List findEntities(CustomerId id, TimePageLink pageLink) { + protected List findEntities(Customer customer, TimePageLink pageLink) { try { - return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink).get(); + 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 [{}].", tenantId, id); + 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) { - updateAssignedCustomerTitle(new DashboardId(entity.getUuidId()), this.customerId, this.customerTitle); + updateAssignedCustomer(new DashboardId(entity.getUuidId()), this.customer); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java index 8590c2a36f..0327232f37 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java @@ -20,18 +20,22 @@ 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.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.util.HashMap; +import java.io.IOException; +import java.util.HashSet; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.*; @@ -43,6 +47,8 @@ import static org.thingsboard.server.dao.model.ModelConstants.*; public final class DashboardEntity implements SearchTextEntity { 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) @@ -58,8 +64,8 @@ public final class DashboardEntity implements SearchTextEntity { @Column(name = SEARCH_TEXT_PROPERTY) private String searchText; - @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY, codec = JsonCodec.class) - private JsonNode assignedCustomers; + @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY) + private String assignedCustomers; @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class) private JsonNode configuration; @@ -77,7 +83,11 @@ public final class DashboardEntity implements SearchTextEntity { } this.title = dashboard.getTitle(); if (dashboard.getAssignedCustomers() != null) { - this.assignedCustomers = objectMapper.valueToTree(dashboard.getAssignedCustomers()); + try { + this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers()); + } catch (JsonProcessingException e) { + log.error("Unable to serialize assigned customers to string!", e); + } } this.configuration = dashboard.getConfiguration(); } @@ -106,11 +116,11 @@ public final class DashboardEntity implements SearchTextEntity { this.title = title; } - public JsonNode getAssignedCustomers() { + public String getAssignedCustomers() { return assignedCustomers; } - public void setAssignedCustomers(JsonNode assignedCustomers) { + public void setAssignedCustomers(String assignedCustomers) { this.assignedCustomers = assignedCustomers; } @@ -144,10 +154,10 @@ public final class DashboardEntity implements SearchTextEntity { dashboard.setTenantId(new TenantId(tenantId)); } dashboard.setTitle(title); - if (assignedCustomers != null) { + if (!StringUtils.isEmpty(assignedCustomers)) { try { - dashboard.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class)); - } catch (JsonProcessingException e) { + dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType)); + } catch (IOException e) { log.warn("Unable to parse assigned customers!", e); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java index 609f3bb598..f64bc44dc1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java @@ -20,20 +20,20 @@ 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.JsonNode; +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 org.thingsboard.server.dao.model.type.JsonCodec; -import java.util.HashMap; -import java.util.Set; +import java.io.IOException; +import java.util.HashSet; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.*; @@ -45,6 +45,8 @@ import static org.thingsboard.server.dao.model.ModelConstants.*; public class DashboardInfoEntity implements SearchTextEntity { 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) @@ -60,8 +62,8 @@ public class DashboardInfoEntity implements SearchTextEntity { @Column(name = SEARCH_TEXT_PROPERTY) private String searchText; - @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY, codec = JsonCodec.class) - private JsonNode assignedCustomers; + @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY) + private String assignedCustomers; public DashboardInfoEntity() { super(); @@ -76,7 +78,11 @@ public class DashboardInfoEntity implements SearchTextEntity { } this.title = dashboardInfo.getTitle(); if (dashboardInfo.getAssignedCustomers() != null) { - this.assignedCustomers = objectMapper.valueToTree(dashboardInfo.getAssignedCustomers()); + try { + this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers()); + } catch (JsonProcessingException e) { + log.error("Unable to serialize assigned customers to string!", e); + } } } @@ -104,11 +110,11 @@ public class DashboardInfoEntity implements SearchTextEntity { this.title = title; } - public JsonNode getAssignedCustomers() { + public String getAssignedCustomers() { return assignedCustomers; } - public void setAssignedCustomers(JsonNode assignedCustomers) { + public void setAssignedCustomers(String assignedCustomers) { this.assignedCustomers = assignedCustomers; } @@ -134,10 +140,10 @@ public class DashboardInfoEntity implements SearchTextEntity { dashboardInfo.setTenantId(new TenantId(tenantId)); } dashboardInfo.setTitle(title); - if (assignedCustomers != null) { + if (!StringUtils.isEmpty(assignedCustomers)) { try { - dashboardInfo.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class)); - } catch (JsonProcessingException e) { + dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType)); + } catch (IOException e) { log.warn("Unable to parse assigned customers!", e); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java index 6f7810bf55..99f65f0a32 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java @@ -17,6 +17,7 @@ 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; @@ -24,8 +25,9 @@ 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; @@ -36,9 +38,8 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; -import java.util.HashMap; -import java.util.List; -import java.util.Set; +import java.io.IOException; +import java.util.HashSet; @Data @Slf4j @@ -49,6 +50,8 @@ import java.util.Set; public final class DashboardEntity extends BaseSqlEntity implements SearchTextEntity { 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; @@ -59,9 +62,8 @@ public final class DashboardEntity extends BaseSqlEntity implements S @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) private String searchText; - @Type(type = "json") @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY) - private JsonNode assignedCustomers; + private String assignedCustomers; @Type(type = "json") @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY) @@ -80,7 +82,11 @@ public final class DashboardEntity extends BaseSqlEntity implements S } this.title = dashboard.getTitle(); if (dashboard.getAssignedCustomers() != null) { - this.assignedCustomers = objectMapper.valueToTree(dashboard.getAssignedCustomers()); + try { + this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers()); + } catch (JsonProcessingException e) { + log.error("Unable to serialize assigned customers to string!", e); + } } this.configuration = dashboard.getConfiguration(); } @@ -103,10 +109,10 @@ public final class DashboardEntity extends BaseSqlEntity implements S dashboard.setTenantId(new TenantId(toUUID(tenantId))); } dashboard.setTitle(title); - if (assignedCustomers != null) { + if (!StringUtils.isEmpty(assignedCustomers)) { try { - dashboard.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class)); - } catch (JsonProcessingException e) { + dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType)); + } catch (IOException e) { log.warn("Unable to parse assigned customers!", e); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java index d9b8efe8f4..7c295d1bab 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java @@ -17,14 +17,14 @@ 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.JsonNode; +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.hibernate.annotations.Type; +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; @@ -34,8 +34,8 @@ import org.thingsboard.server.dao.model.SearchTextEntity; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; -import java.util.HashMap; -import java.util.Set; +import java.io.IOException; +import java.util.HashSet; @Data @Slf4j @@ -45,6 +45,8 @@ import java.util.Set; public class DashboardInfoEntity extends BaseSqlEntity implements SearchTextEntity { 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; @@ -55,9 +57,8 @@ public class DashboardInfoEntity extends BaseSqlEntity implements @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) private String searchText; - @Type(type = "json") @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY) - private JsonNode assignedCustomers; + private String assignedCustomers; public DashboardInfoEntity() { super(); @@ -72,7 +73,11 @@ public class DashboardInfoEntity extends BaseSqlEntity implements } this.title = dashboardInfo.getTitle(); if (dashboardInfo.getAssignedCustomers() != null) { - this.assignedCustomers = objectMapper.valueToTree(dashboardInfo.getAssignedCustomers()); + try { + this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers()); + } catch (JsonProcessingException e) { + log.error("Unable to serialize assigned customers to string!", e); + } } } @@ -98,10 +103,10 @@ public class DashboardInfoEntity extends BaseSqlEntity implements dashboardInfo.setTenantId(new TenantId(toUUID(tenantId))); } dashboardInfo.setTitle(title); - if (assignedCustomers != null) { + if (!StringUtils.isEmpty(assignedCustomers)) { try { - dashboardInfo.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class)); - } catch (JsonProcessingException e) { + dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType)); + } catch (IOException e) { log.warn("Unable to parse assigned customers!", e); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java index 0427fb44f7..380c65e8f7 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java @@ -320,7 +320,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { Assert.assertEquals(dashboards, loadedDashboards); - dashboardService.unassignCustomerDashboards(tenantId, customerId); + dashboardService.unassignCustomerDashboards(customerId); pageLink = new TimePageLink(42); pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get(); diff --git a/ui/src/app/api/dashboard.service.js b/ui/src/app/api/dashboard.service.js index 3082bd3811..ae11f84e8b 100644 --- a/ui/src/app/api/dashboard.service.js +++ b/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; + } + } diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index df1c3e0bc9..d30c10cc37 100644 --- a/ui/src/app/api/entity.service.js +++ b/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; } diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js index ce5e673f18..c969c2f48c 100644 --- a/ui/src/app/api/user.service.js +++ b/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) { diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index d82add14c0..6f09f3417b 100644 --- a/ui/src/app/common/types.constant.js +++ b/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: { diff --git a/ui/src/app/components/dashboard-autocomplete.directive.js b/ui/src/app/components/dashboard-autocomplete.directive.js index 2235b82e95..6af55853b2 100644 --- a/ui/src/app/components/dashboard-autocomplete.directive.js +++ b/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}); } } diff --git a/ui/src/app/components/dashboard-select.directive.js b/ui/src/app/components/dashboard-select.directive.js index ac5cd3d5b1..b1721687a6 100644 --- a/ui/src/app/components/dashboard-select.directive.js +++ b/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) { diff --git a/ui/src/app/dashboard/add-dashboards-to-customer.controller.js b/ui/src/app/dashboard/add-dashboards-to-customer.controller.js index 6fe6e7932e..b0bbef1b75 100644 --- a/ui/src/app/dashboard/add-dashboards-to-customer.controller.js +++ b/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; diff --git a/ui/src/app/dashboard/assign-to-customer.controller.js b/ui/src/app/dashboard/assign-to-customer.controller.js deleted file mode 100644 index fa2cd0505d..0000000000 --- a/ui/src/app/dashboard/assign-to-customer.controller.js +++ /dev/null @@ -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 - }; - } -} diff --git a/ui/src/app/dashboard/assign-to-customer.tpl.html b/ui/src/app/dashboard/assign-to-customer.tpl.html deleted file mode 100644 index 4d7ba85452..0000000000 --- a/ui/src/app/dashboard/assign-to-customer.tpl.html +++ /dev/null @@ -1,76 +0,0 @@ - - -
- -
-

dashboard.assign-dashboard-to-customer

- - - - -
-
- - - -
-
- dashboard.assign-to-customer-text - - - - search - - - -
- customer.no-customers-text - - - - - {{ customer.title }} - - - -
-
-
-
- - - - {{ 'action.assign' | translate }} - - {{ 'action.cancel' | - translate }} - - -
-
\ No newline at end of file diff --git a/ui/src/app/dashboard/dashboard-card.scss b/ui/src/app/dashboard/dashboard-card.scss new file mode 100644 index 0000000000..32f25c7aab --- /dev/null +++ b/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; +} + diff --git a/ui/src/app/dashboard/dashboard-card.tpl.html b/ui/src/app/dashboard/dashboard-card.tpl.html index 636786758e..ed19fda07a 100644 --- a/ui/src/app/dashboard/dashboard-card.tpl.html +++ b/ui/src/app/dashboard/dashboard-card.tpl.html @@ -15,6 +15,6 @@ limitations under the License. --> -
{{'dashboard.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'
-
{{'dashboard.public' | translate}}
+
{{'dashboard.assignedToCustomers' | translate}}: '{{vm.item.assignedCustomersText}}'
+
{{'dashboard.public' | translate}}
diff --git a/ui/src/app/dashboard/dashboard-fieldset.tpl.html b/ui/src/app/dashboard/dashboard-fieldset.tpl.html index a54f477d11..00ee554d84 100644 --- a/ui/src/app/dashboard/dashboard-fieldset.tpl.html +++ b/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 }} {{ 'dashboard.make-public' | translate }} -{{ 'dashboard.assign-to-customer' | translate }} -{{ isPublic ? 'dashboard.make-private' : 'dashboard.unassign-from-customer' | translate }} +{{ 'dashboard.make-private' | translate }} +{{ 'dashboard.manage-assigned-customers' | translate }} +{{ 'dashboard.unassign-from-customer' | translate }} {{ 'dashboard.delete' | translate }} - - + ng-show="!isEdit && dashboard.assignedCustomersText && dashboardScope === 'tenant'"> + + -
+
diff --git a/ui/src/app/dashboard/index.js b/ui/src/app/dashboard/index.js index 40897164fa..ac63dc55a2 100644 --- a/ui/src/app/dashboard/index.js +++ b/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) diff --git a/ui/src/app/dashboard/manage-assigned-customers.controller.js b/ui/src/app/dashboard/manage-assigned-customers.controller.js new file mode 100644 index 0000000000..90d3e6dc84 --- /dev/null +++ b/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.titleText}}

+ + + + +
+
+ + + +
+
+ {{vm.labelText}} + +
+
+
+ + + + {{ vm.actionName | translate }} + + {{ 'action.cancel' | + translate }} + + +
+
\ No newline at end of file diff --git a/ui/src/app/entity/entity-autocomplete.directive.js b/ui/src/app/entity/entity-autocomplete.directive.js index 62f3d6d33e..2d114dbb2e 100644 --- a/ui/src/app/entity/entity-autocomplete.directive.js +++ b/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) { diff --git a/ui/src/app/entity/entity-filter.tpl.html b/ui/src/app/entity/entity-filter.tpl.html index 9f62454915..0e8ee1177b 100644 --- a/ui/src/app/entity/entity-filter.tpl.html +++ b/ui/src/app/entity/entity-filter.tpl.html @@ -32,6 +32,7 @@ @@ -78,6 +79,7 @@
@@ -123,6 +125,7 @@ the-form="theForm" tb-required="!filter.rootStateEntity" ng-disabled="filter.rootStateEntity" + use-alias-entity-types="true" ng-model="filter.rootEntity">
@@ -139,6 +142,7 @@ @@ -182,6 +186,7 @@ the-form="theForm" tb-required="!filter.rootStateEntity" ng-disabled="filter.rootStateEntity" + use-alias-entity-types="true" ng-model="filter.rootEntity"> @@ -198,6 +203,7 @@ @@ -249,6 +255,7 @@ the-form="theForm" tb-required="!filter.rootStateEntity" ng-disabled="filter.rootStateEntity" + use-alias-entity-types="true" ng-model="filter.rootEntity"> @@ -265,6 +272,7 @@ diff --git a/ui/src/app/entity/entity-select.directive.js b/ui/src/app/entity/entity-select.directive.js index 2778a79a17..8e2fbf9674 100644 --- a/ui/src/app/entity/entity-select.directive.js +++ b/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: "=?" } }; } diff --git a/ui/src/app/entity/entity-select.tpl.html b/ui/src/app/entity/entity-select.tpl.html index 13e17e7af1..d37020220d 100644 --- a/ui/src/app/entity/entity-select.tpl.html +++ b/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"> Date: Thu, 1 Mar 2018 12:58:57 +0200 Subject: [PATCH 5/5] Fix SQL upgrade script. --- .../service/install/sql/SqlDbHelper.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java b/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java index c78ceda02d..669646f1fb 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java +++ b/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java @@ -45,11 +45,7 @@ public class SqlDbHelper { public static Path dumpTableIfExists(Connection conn, String tableName, String[] columns, String[] defaultValues, String dumpPrefix, boolean printHeader) throws Exception { - DatabaseMetaData metaData = conn.getMetaData(); - ResultSet res = metaData.getTables(null, null, tableName, - new String[] {"TABLE"}); - if (res.next()) { - res.close(); + if (tableExists(conn, tableName)) { Path dumpFile = Files.createTempFile(dumpPrefix, null); Files.deleteIfExists(dumpFile); CSVFormat csvFormat = CSV_DUMP_FORMAT; @@ -61,9 +57,9 @@ public class SqlDbHelper { try (ResultSet tableRes = stmt.executeQuery()) { ResultSetMetaData resMetaData = tableRes.getMetaData(); Map columnIndexMap = new HashMap<>(); - for (int i = 0; i < resMetaData.getColumnCount(); i++) { + for (int i = 1; i <= resMetaData.getColumnCount(); i++) { String columnName = resMetaData.getColumnName(i); - columnIndexMap.put(columnName, i); + columnIndexMap.put(columnName.toUpperCase(), i); } while(tableRes.next()) { dumpRow(tableRes, columnIndexMap, columns, defaultValues, csvPrinter); @@ -77,6 +73,15 @@ public class SqlDbHelper { } } + 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); } @@ -89,7 +94,6 @@ public class SqlDbHelper { csvFormat = CSV_DUMP_FORMAT.withHeader(columns); } try (PreparedStatement prepared = conn.prepareStatement(createInsertStatement(tableName, columns))) { - prepared.getParameterMetaData(); try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), csvFormat)) { csvParser.forEach(record -> { try { @@ -122,13 +126,13 @@ public class SqlDbHelper { } private static String getColumnValue(String column, String defaultValue, Map columnIndexMap, ResultSet res) { - int index = columnIndexMap.containsKey(column) ? columnIndexMap.get(column) : -1; + 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) { - str = ""; + return null; } else { str = obj.toString(); }