Commit e1afbe6667524d290895e9ece4c29dc731d1aedf

Authored by Igor Kulikov
1 parent 74aca329

Control visibility and order of dashboard in mobile application. Ability to hide…

… widgets in mobile mode. Fix dashboard state changes processing.
Showing 28 changed files with 301 additions and 25 deletions
... ... @@ -142,7 +142,9 @@ CREATE TABLE IF NOT EXISTS oauth2_mobile (
142 142 );
143 143
144 144 ALTER TABLE dashboard
145   - ADD COLUMN IF NOT EXISTS image varchar(1000000);
  145 + ADD COLUMN IF NOT EXISTS image varchar(1000000),
  146 + ADD COLUMN IF NOT EXISTS mobile_hide boolean DEFAULT false,
  147 + ADD COLUMN IF NOT EXISTS mobile_order int;
146 148
147 149 ALTER TABLE device_profile
148 150 ADD COLUMN IF NOT EXISTS image varchar(1000000),
... ... @@ -210,4 +212,3 @@ CREATE TABLE IF NOT EXISTS rpc (
210 212 );
211 213
212 214 CREATE INDEX IF NOT EXISTS idx_rpc_tenant_id_device_id ON rpc(tenant_id, device_id);
213   -
... ...
... ... @@ -468,13 +468,18 @@ public class DashboardController extends BaseController {
468 468 public PageData<DashboardInfo> getTenantDashboards(
469 469 @RequestParam int pageSize,
470 470 @RequestParam int page,
  471 + @RequestParam(required = false) Boolean mobile,
471 472 @RequestParam(required = false) String textSearch,
472 473 @RequestParam(required = false) String sortProperty,
473 474 @RequestParam(required = false) String sortOrder) throws ThingsboardException {
474 475 try {
475 476 TenantId tenantId = getCurrentUser().getTenantId();
476 477 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
477   - return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink));
  478 + if (mobile != null && mobile.booleanValue()) {
  479 + return checkNotNull(dashboardService.findMobileDashboardsByTenantId(tenantId, pageLink));
  480 + } else {
  481 + return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink));
  482 + }
478 483 } catch (Exception e) {
479 484 throw handleException(e);
480 485 }
... ... @@ -487,6 +492,7 @@ public class DashboardController extends BaseController {
487 492 @PathVariable("customerId") String strCustomerId,
488 493 @RequestParam int pageSize,
489 494 @RequestParam int page,
  495 + @RequestParam(required = false) Boolean mobile,
490 496 @RequestParam(required = false) String textSearch,
491 497 @RequestParam(required = false) String sortProperty,
492 498 @RequestParam(required = false) String sortOrder) throws ThingsboardException {
... ... @@ -496,7 +502,11 @@ public class DashboardController extends BaseController {
496 502 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
497 503 checkCustomerId(customerId, Operation.READ);
498 504 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
499   - return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
  505 + if (mobile != null && mobile.booleanValue()) {
  506 + return checkNotNull(dashboardService.findMobileDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
  507 + } else {
  508 + return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
  509 + }
500 510 } catch (Exception e) {
501 511 throw handleException(e);
502 512 }
... ...
... ... @@ -45,10 +45,14 @@ public interface DashboardService {
45 45
46 46 PageData<DashboardInfo> findDashboardsByTenantId(TenantId tenantId, PageLink pageLink);
47 47
  48 + PageData<DashboardInfo> findMobileDashboardsByTenantId(TenantId tenantId, PageLink pageLink);
  49 +
48 50 void deleteDashboardsByTenantId(TenantId tenantId);
49 51
50 52 PageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink);
51 53
  54 + PageData<DashboardInfo> findMobileDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink);
  55 +
52 56 void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId);
53 57
54 58 void updateCustomerDashboards(TenantId tenantId, CustomerId customerId);
... ...
... ... @@ -33,6 +33,8 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
33 33 private String image;
34 34 @Valid
35 35 private Set<ShortCustomerInfo> assignedCustomers;
  36 + private boolean mobileHide;
  37 + private Integer mobileOrder;
36 38
37 39 public DashboardInfo() {
38 40 super();
... ... @@ -48,6 +50,8 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
48 50 this.title = dashboardInfo.getTitle();
49 51 this.image = dashboardInfo.getImage();
50 52 this.assignedCustomers = dashboardInfo.getAssignedCustomers();
  53 + this.mobileHide = dashboardInfo.isMobileHide();
  54 + this.mobileOrder = dashboardInfo.getMobileOrder();
51 55 }
52 56
53 57 public TenantId getTenantId() {
... ... @@ -82,10 +86,27 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
82 86 this.assignedCustomers = assignedCustomers;
83 87 }
84 88
  89 + public boolean isMobileHide() {
  90 + return mobileHide;
  91 + }
  92 +
  93 + public void setMobileHide(boolean mobileHide) {
  94 + this.mobileHide = mobileHide;
  95 + }
  96 +
  97 + public Integer getMobileOrder() {
  98 + return mobileOrder;
  99 + }
  100 +
  101 + public void setMobileOrder(Integer mobileOrder) {
  102 + this.mobileOrder = mobileOrder;
  103 + }
  104 +
85 105 public boolean isAssignedToCustomer(CustomerId customerId) {
86 106 return this.assignedCustomers != null && this.assignedCustomers.contains(new ShortCustomerInfo(customerId, null, false));
87 107 }
88 108
  109 +
89 110 public ShortCustomerInfo getAssignedCustomerInfo(CustomerId customerId) {
90 111 if (this.assignedCustomers != null) {
91 112 for (ShortCustomerInfo customerInfo : this.assignedCustomers) {
... ...
... ... @@ -32,6 +32,7 @@ import java.util.List;
32 32 import java.util.Map;
33 33 import java.util.Optional;
34 34 import java.util.UUID;
  35 +import java.util.stream.Collectors;
35 36
36 37 public abstract class DaoUtil {
37 38
... ... @@ -55,6 +56,14 @@ public abstract class DaoUtil {
55 56 return PageRequest.of(pageLink.getPage(), pageLink.getPageSize(), toSort(pageLink.getSortOrder(), columnMap));
56 57 }
57 58
  59 + public static Pageable toPageable(PageLink pageLink, List<SortOrder> sortOrders) {
  60 + return toPageable(pageLink, Collections.emptyMap(), sortOrders);
  61 + }
  62 +
  63 + public static Pageable toPageable(PageLink pageLink, Map<String,String> columnMap, List<SortOrder> sortOrders) {
  64 + return PageRequest.of(pageLink.getPage(), pageLink.getPageSize(), toSort(sortOrders, columnMap));
  65 + }
  66 +
58 67 public static Sort toSort(SortOrder sortOrder) {
59 68 return toSort(sortOrder, Collections.emptyMap());
60 69 }
... ... @@ -71,6 +80,26 @@ public abstract class DaoUtil {
71 80 }
72 81 }
73 82
  83 + public static Sort toSort(List<SortOrder> sortOrders) {
  84 + return toSort(sortOrders, Collections.emptyMap());
  85 + }
  86 +
  87 + public static Sort toSort(List<SortOrder> sortOrders, Map<String,String> columnMap) {
  88 + return toSort(sortOrders, columnMap, Sort.NullHandling.NULLS_LAST);
  89 + }
  90 +
  91 + public static Sort toSort(List<SortOrder> sortOrders, Map<String,String> columnMap, Sort.NullHandling nullHandlingHint) {
  92 + return Sort.by(sortOrders.stream().map(s -> toSortOrder(s, columnMap, nullHandlingHint)).collect(Collectors.toList()));
  93 + }
  94 +
  95 + public static Sort.Order toSortOrder(SortOrder sortOrder, Map<String,String> columnMap, Sort.NullHandling nullHandlingHint) {
  96 + String property = sortOrder.getProperty();
  97 + if (columnMap.containsKey(property)) {
  98 + property = columnMap.get(property);
  99 + }
  100 + return new Sort.Order(Sort.Direction.fromString(sortOrder.getDirection().name()), property, nullHandlingHint);
  101 + }
  102 +
74 103 public static <T> List<T> convertDataList(Collection<? extends ToData<T>> toDataList) {
75 104 List<T> list = Collections.emptyList();
76 105 if (toDataList != null && !toDataList.isEmpty()) {
... ...
... ... @@ -37,6 +37,15 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> {
37 37 PageData<DashboardInfo> findDashboardsByTenantId(UUID tenantId, PageLink pageLink);
38 38
39 39 /**
  40 + * Find dashboards not hidden for mobile by tenantId and page link.
  41 + *
  42 + * @param tenantId the tenantId
  43 + * @param pageLink the page link
  44 + * @return the list of dashboard objects
  45 + */
  46 + PageData<DashboardInfo> findMobileDashboardsByTenantId(UUID tenantId, PageLink pageLink);
  47 +
  48 + /**
40 49 * Find dashboards by tenantId, customerId and page link.
41 50 *
42 51 * @param tenantId the tenantId
... ... @@ -47,6 +56,16 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> {
47 56 PageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink);
48 57
49 58 /**
  59 + * Find dashboards not hidden for mobile by tenantId, customerId and page link.
  60 + *
  61 + * @param tenantId the tenantId
  62 + * @param customerId the customerId
  63 + * @param pageLink the page link
  64 + * @return the list of dashboard objects
  65 + */
  66 + PageData<DashboardInfo> findMobileDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink);
  67 +
  68 + /**
50 69 * Find dashboards by tenantId, edgeId and page link.
51 70 *
52 71 * @param tenantId the tenantId
... ...
... ... @@ -187,6 +187,14 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
187 187 }
188 188
189 189 @Override
  190 + public PageData<DashboardInfo> findMobileDashboardsByTenantId(TenantId tenantId, PageLink pageLink) {
  191 + log.trace("Executing findMobileDashboardsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
  192 + Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  193 + Validator.validatePageLink(pageLink);
  194 + return dashboardInfoDao.findMobileDashboardsByTenantId(tenantId.getId(), pageLink);
  195 + }
  196 +
  197 + @Override
190 198 public void deleteDashboardsByTenantId(TenantId tenantId) {
191 199 log.trace("Executing deleteDashboardsByTenantId, tenantId [{}]", tenantId);
192 200 Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
... ... @@ -203,6 +211,15 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
203 211 }
204 212
205 213 @Override
  214 + public PageData<DashboardInfo> findMobileDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) {
  215 + log.trace("Executing findMobileDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink);
  216 + Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  217 + Validator.validateId(customerId, "Incorrect customerId " + customerId);
  218 + Validator.validatePageLink(pageLink);
  219 + return dashboardInfoDao.findMobileDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
  220 + }
  221 +
  222 + @Override
206 223 public void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId) {
207 224 log.trace("Executing unassignCustomerDashboards, customerId [{}]", customerId);
208 225 Validator.validateId(customerId, "Incorrect customerId " + customerId);
... ...
... ... @@ -339,6 +339,8 @@ public class ModelConstants {
339 339 public static final String DASHBOARD_IMAGE_PROPERTY = "image";
340 340 public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration";
341 341 public static final String DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY = "assigned_customers";
  342 + public static final String DASHBOARD_MOBILE_HIDE_PROPERTY = "mobile_hide";
  343 + public static final String DASHBOARD_MOBILE_ORDER_PROPERTY = "mobile_order";
342 344
343 345 public static final String DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_tenant_and_search_text";
344 346
... ...
... ... @@ -68,6 +68,12 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
68 68 @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
69 69 private String assignedCustomers;
70 70
  71 + @Column(name = ModelConstants.DASHBOARD_MOBILE_HIDE_PROPERTY)
  72 + private boolean mobileHide;
  73 +
  74 + @Column(name = ModelConstants.DASHBOARD_MOBILE_ORDER_PROPERTY)
  75 + private Integer mobileOrder;
  76 +
71 77 @Type(type = "json")
72 78 @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY)
73 79 private JsonNode configuration;
... ... @@ -93,6 +99,8 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
93 99 log.error("Unable to serialize assigned customers to string!", e);
94 100 }
95 101 }
  102 + this.mobileHide = dashboard.isMobileHide();
  103 + this.mobileOrder = dashboard.getMobileOrder();
96 104 this.configuration = dashboard.getConfiguration();
97 105 }
98 106
... ... @@ -122,6 +130,8 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
122 130 log.warn("Unable to parse assigned customers!", e);
123 131 }
124 132 }
  133 + dashboard.setMobileHide(mobileHide);
  134 + dashboard.setMobileOrder(mobileOrder);
125 135 dashboard.setConfiguration(configuration);
126 136 return dashboard;
127 137 }
... ...
... ... @@ -63,6 +63,12 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
63 63 @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
64 64 private String assignedCustomers;
65 65
  66 + @Column(name = ModelConstants.DASHBOARD_MOBILE_HIDE_PROPERTY)
  67 + private boolean mobileHide;
  68 +
  69 + @Column(name = ModelConstants.DASHBOARD_MOBILE_ORDER_PROPERTY)
  70 + private Integer mobileOrder;
  71 +
66 72 public DashboardInfoEntity() {
67 73 super();
68 74 }
... ... @@ -84,6 +90,8 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
84 90 log.error("Unable to serialize assigned customers to string!", e);
85 91 }
86 92 }
  93 + this.mobileHide = dashboardInfo.isMobileHide();
  94 + this.mobileOrder = dashboardInfo.getMobileOrder();
87 95 }
88 96
89 97 @Override
... ... @@ -116,6 +124,8 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
116 124 log.warn("Unable to parse assigned customers!", e);
117 125 }
118 126 }
  127 + dashboardInfo.setMobileHide(mobileHide);
  128 + dashboardInfo.setMobileOrder(mobileOrder);
119 129 return dashboardInfo;
120 130 }
121 131
... ...
... ... @@ -37,6 +37,13 @@ public interface DashboardInfoRepository extends PagingAndSortingRepository<Dash
37 37 @Param("searchText") String searchText,
38 38 Pageable pageable);
39 39
  40 + @Query("SELECT di FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId " +
  41 + "AND di.mobileHide = false " +
  42 + "AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
  43 + Page<DashboardInfoEntity> findMobileByTenantId(@Param("tenantId") UUID tenantId,
  44 + @Param("searchText") String searchText,
  45 + Pageable pageable);
  46 +
40 47 @Query("SELECT di FROM DashboardInfoEntity di, RelationEntity re WHERE di.tenantId = :tenantId " +
41 48 "AND di.id = re.toId AND re.toType = 'DASHBOARD' AND re.relationTypeGroup = 'DASHBOARD' " +
42 49 "AND re.relationType = 'Contains' AND re.fromId = :customerId AND re.fromType = 'CUSTOMER' " +
... ... @@ -47,6 +54,16 @@ public interface DashboardInfoRepository extends PagingAndSortingRepository<Dash
47 54 Pageable pageable);
48 55
49 56 @Query("SELECT di FROM DashboardInfoEntity di, RelationEntity re WHERE di.tenantId = :tenantId " +
  57 + "AND di.mobileHide = false " +
  58 + "AND di.id = re.toId AND re.toType = 'DASHBOARD' AND re.relationTypeGroup = 'DASHBOARD' " +
  59 + "AND re.relationType = 'Contains' AND re.fromId = :customerId AND re.fromType = 'CUSTOMER' " +
  60 + "AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
  61 + Page<DashboardInfoEntity> findMobileByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId,
  62 + @Param("customerId") UUID customerId,
  63 + @Param("searchText") String searchText,
  64 + Pageable pageable);
  65 +
  66 + @Query("SELECT di FROM DashboardInfoEntity di, RelationEntity re WHERE di.tenantId = :tenantId " +
50 67 "AND di.id = re.toId AND re.toType = 'DASHBOARD' AND re.relationTypeGroup = 'EDGE' " +
51 68 "AND re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " +
52 69 "AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
... ...
... ... @@ -22,12 +22,15 @@ import org.springframework.stereotype.Component;
22 22 import org.thingsboard.server.common.data.DashboardInfo;
23 23 import org.thingsboard.server.common.data.page.PageData;
24 24 import org.thingsboard.server.common.data.page.PageLink;
  25 +import org.thingsboard.server.common.data.page.SortOrder;
25 26 import org.thingsboard.server.dao.DaoUtil;
26 27 import org.thingsboard.server.dao.dashboard.DashboardInfoDao;
27 28 import org.thingsboard.server.dao.model.sql.DashboardInfoEntity;
28 29 import org.thingsboard.server.dao.relation.RelationDao;
29 30 import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
30 31
  32 +import java.util.ArrayList;
  33 +import java.util.List;
31 34 import java.util.Objects;
32 35 import java.util.UUID;
33 36
... ... @@ -41,9 +44,6 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
41 44 @Autowired
42 45 private DashboardInfoRepository dashboardInfoRepository;
43 46
44   - @Autowired
45   - private RelationDao relationDao;
46   -
47 47 @Override
48 48 protected Class<DashboardInfoEntity> getEntityClass() {
49 49 return DashboardInfoEntity.class;
... ... @@ -64,6 +64,20 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
64 64 }
65 65
66 66 @Override
  67 + public PageData<DashboardInfo> findMobileDashboardsByTenantId(UUID tenantId, PageLink pageLink) {
  68 + List<SortOrder> sortOrders = new ArrayList<>();
  69 + sortOrders.add(new SortOrder("mobileOrder", SortOrder.Direction.ASC));
  70 + if (pageLink.getSortOrder() != null) {
  71 + sortOrders.add(pageLink.getSortOrder());
  72 + }
  73 + return DaoUtil.toPageData(dashboardInfoRepository
  74 + .findMobileByTenantId(
  75 + tenantId,
  76 + Objects.toString(pageLink.getTextSearch(), ""),
  77 + DaoUtil.toPageable(pageLink, sortOrders)));
  78 + }
  79 +
  80 + @Override
67 81 public PageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink) {
68 82 return DaoUtil.toPageData(dashboardInfoRepository
69 83 .findByTenantIdAndCustomerId(
... ... @@ -74,6 +88,21 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
74 88 }
75 89
76 90 @Override
  91 + public PageData<DashboardInfo> findMobileDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink) {
  92 + List<SortOrder> sortOrders = new ArrayList<>();
  93 + sortOrders.add(new SortOrder("mobileOrder", SortOrder.Direction.ASC));
  94 + if (pageLink.getSortOrder() != null) {
  95 + sortOrders.add(pageLink.getSortOrder());
  96 + }
  97 + return DaoUtil.toPageData(dashboardInfoRepository
  98 + .findMobileByTenantIdAndCustomerId(
  99 + tenantId,
  100 + customerId,
  101 + Objects.toString(pageLink.getTextSearch(), ""),
  102 + DaoUtil.toPageable(pageLink, sortOrders)));
  103 + }
  104 +
  105 + @Override
77 106 public PageData<DashboardInfo> findDashboardsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, PageLink pageLink) {
78 107 log.debug("Try to find dashboards by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);
79 108 return DaoUtil.toPageData(dashboardInfoRepository
... ...
... ... @@ -123,6 +123,8 @@ CREATE TABLE IF NOT EXISTS dashboard (
123 123 search_text varchar(255),
124 124 tenant_id uuid,
125 125 title varchar(255),
  126 + mobile_hide boolean DEFAULT false,
  127 + mobile_order int,
126 128 image varchar(1000000)
127 129 );
128 130
... ...
... ... @@ -138,6 +138,8 @@ CREATE TABLE IF NOT EXISTS dashboard (
138 138 search_text varchar(255),
139 139 tenant_id uuid,
140 140 title varchar(255),
  141 + mobile_hide boolean DEFAULT false,
  142 + mobile_order int,
141 143 image varchar(1000000)
142 144 );
143 145
... ...
... ... @@ -31,12 +31,14 @@ import org.thingsboard.server.common.data.id.EdgeId;
31 31 import org.thingsboard.server.common.data.id.TenantId;
32 32 import org.thingsboard.server.common.data.page.PageData;
33 33 import org.thingsboard.server.common.data.page.PageLink;
  34 +import org.thingsboard.server.common.data.page.SortOrder;
34 35 import org.thingsboard.server.common.data.page.TimePageLink;
35 36 import org.thingsboard.server.dao.exception.DataValidationException;
36 37
37 38 import java.io.IOException;
38 39 import java.util.ArrayList;
39 40 import java.util.Collections;
  41 +import java.util.Comparator;
40 42 import java.util.List;
41 43 import java.util.concurrent.ExecutionException;
42 44
... ... @@ -204,6 +206,66 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
204 206
205 207 tenantService.deleteTenant(tenantId);
206 208 }
  209 +
  210 + @Test
  211 + public void testFindMobileDashboardsByTenantId() {
  212 + Tenant tenant = new Tenant();
  213 + tenant.setTitle("Test tenant");
  214 + tenant = tenantService.saveTenant(tenant);
  215 +
  216 + TenantId tenantId = tenant.getId();
  217 +
  218 + List<DashboardInfo> mobileDashboards = new ArrayList<>();
  219 + for (int i=0;i<165;i++) {
  220 + Dashboard dashboard = new Dashboard();
  221 + dashboard.setTenantId(tenantId);
  222 + dashboard.setTitle("Dashboard"+i);
  223 + dashboard.setMobileHide(i % 2 == 0);
  224 + if (!dashboard.isMobileHide()) {
  225 + dashboard.setMobileOrder(i % 4 == 0 ? (int)(Math.random() * 100) : null);
  226 + }
  227 + Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
  228 + if (!dashboard.isMobileHide()) {
  229 + mobileDashboards.add(new DashboardInfo(savedDashboard));
  230 + }
  231 + }
  232 +
  233 + List<DashboardInfo> loadedMobileDashboards = new ArrayList<>();
  234 + PageLink pageLink = new PageLink(16, 0, null, new SortOrder("createdTime", SortOrder.Direction.ASC));
  235 + PageData<DashboardInfo> pageData = null;
  236 + do {
  237 + pageData = dashboardService.findMobileDashboardsByTenantId(tenantId, pageLink);
  238 + loadedMobileDashboards.addAll(pageData.getData());
  239 + if (pageData.hasNext()) {
  240 + pageLink = pageLink.nextPageLink();
  241 + }
  242 + } while (pageData.hasNext());
  243 +
  244 + Collections.sort(mobileDashboards, (o1, o2) -> {
  245 + Integer order1 = o1.getMobileOrder();
  246 + Integer order2 = o2.getMobileOrder();
  247 + if (order1 == null && order2 == null) {
  248 + return o1.getId().getId().compareTo(o2.getId().getId());
  249 + } else if (order1 == null && order2 != null) {
  250 + return 1;
  251 + } else if (order2 == null) {
  252 + return -1;
  253 + } else {
  254 + return order1 - order2;
  255 + }
  256 + });
  257 +
  258 + Assert.assertEquals(mobileDashboards, loadedMobileDashboards);
  259 +
  260 + dashboardService.deleteDashboardsByTenantId(tenantId);
  261 +
  262 + pageLink = new PageLink(31);
  263 + pageData = dashboardService.findMobileDashboardsByTenantId(tenantId, pageLink);
  264 + Assert.assertFalse(pageData.hasNext());
  265 + Assert.assertTrue(pageData.getData().isEmpty());
  266 +
  267 + tenantService.deleteTenant(tenantId);
  268 + }
207 269
208 270 @Test
209 271 public void testFindDashboardsByTenantIdAndTitle() {
... ...
... ... @@ -389,7 +389,8 @@ export class DashboardUtilsService {
389 389 sizeX: originalSize ? originalSize.sizeX : widget.sizeX,
390 390 sizeY: originalSize ? originalSize.sizeY : widget.sizeY,
391 391 mobileOrder: widget.config.mobileOrder,
392   - mobileHeight: widget.config.mobileHeight
  392 + mobileHeight: widget.config.mobileHeight,
  393 + mobileHide: widget.config.mobileHide
393 394 };
394 395 if (isUndefined(originalColumns)) {
395 396 originalColumns = 24;
... ...
... ... @@ -127,6 +127,7 @@ export class AddWidgetDialogComponent extends DialogComponent<AddWidgetDialogCom
127 127 this.widget.config = widgetConfig.config;
128 128 this.widget.config.mobileOrder = widgetConfig.layout.mobileOrder;
129 129 this.widget.config.mobileHeight = widgetConfig.layout.mobileHeight;
  130 + this.widget.config.mobileHide = widgetConfig.layout.mobileHide;
130 131 this.dialogRef.close(this.widget);
131 132 }
132 133 }
... ...
... ... @@ -23,7 +23,7 @@ import { StateControllerComponent } from './state-controller.component';
23 23 import { StatesControllerService } from '@home/components/dashboard-page/states/states-controller.service';
24 24 import { EntityId } from '@app/shared/models/id/entity-id';
25 25 import { UtilsService } from '@core/services/utils.service';
26   -import { base64toObj, objToBase64URI } from '@app/core/utils';
  26 +import { base64toObj, objToBase64 } from '@app/core/utils';
27 27 import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
28 28 import { EntityService } from '@core/http/entity.service';
29 29 import { MobileService } from '@core/services/mobile.service';
... ... @@ -246,7 +246,7 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
246 246
247 247 private updateLocation() {
248 248 if (this.stateObject[0].id) {
249   - const newState = objToBase64URI(this.stateObject);
  249 + const newState = objToBase64(this.stateObject);
250 250 this.updateStateParam(newState);
251 251 }
252 252 }
... ...
... ... @@ -23,7 +23,7 @@ import { StateControllerComponent } from './state-controller.component';
23 23 import { StatesControllerService } from '@home/components/dashboard-page/states/states-controller.service';
24 24 import { EntityId } from '@app/shared/models/id/entity-id';
25 25 import { UtilsService } from '@core/services/utils.service';
26   -import { base64toObj, insertVariable, isEmpty, objToBase64URI } from '@app/core/utils';
  26 +import { base64toObj, insertVariable, isEmpty, objToBase64 } from '@app/core/utils';
27 27 import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
28 28 import { EntityService } from '@core/http/entity.service';
29 29 import { EntityType } from '@shared/models/entity-type.models';
... ... @@ -289,7 +289,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
289 289 if (this.isDefaultState()) {
290 290 newState = null;
291 291 } else {
292   - newState = objToBase64URI(this.stateObject);
  292 + newState = objToBase64(this.stateObject);
293 293 }
294 294 this.updateStateParam(newState, !isStateIdChanged);
295 295 }
... ...
... ... @@ -129,7 +129,7 @@ export abstract class StateControllerComponent implements IStateControllerCompon
129 129 protected updateStateParam(newState: string, replaceCurrentHistoryUrl = false) {
130 130 this.currentState = newState;
131 131 if (this.syncStateWithQueryParam) {
132   - const queryParams: Params = {state: this.currentState};
  132 + const queryParams: Params = {state: encodeURIComponent(this.currentState)};
133 133 this.ngZone.run(() => {
134 134 this.router.navigate(
135 135 [],
... ...
... ... @@ -418,6 +418,9 @@
418 418 <span translate>widget-config.mobile-mode-settings</span>
419 419 <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
420 420 fxLayoutGap="8px">
  421 + <mat-checkbox formControlName="mobileHide">
  422 + {{ 'widget-config.mobile-hide' | translate }}
  423 + </mat-checkbox>
421 424 <mat-form-field fxFlex>
422 425 <mat-label translate>widget-config.order</mat-label>
423 426 <input matInput formControlName="mobileOrder" type="number" step="1">
... ...
... ... @@ -228,7 +228,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
228 228 });
229 229 this.layoutSettings = this.fb.group({
230 230 mobileOrder: [null, [Validators.pattern(/^-?[0-9]+$/)]],
231   - mobileHeight: [null, [Validators.min(1), Validators.max(10), Validators.pattern(/^\d*$/)]]
  231 + mobileHeight: [null, [Validators.min(1), Validators.max(10), Validators.pattern(/^\d*$/)]],
  232 + mobileHide: [false]
232 233 });
233 234 this.actionsSettings = this.fb.group({
234 235 actionsData: [null, []]
... ... @@ -502,7 +503,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
502 503 this.layoutSettings.patchValue(
503 504 {
504 505 mobileOrder: layout.mobileOrder,
505   - mobileHeight: layout.mobileHeight
  506 + mobileHeight: layout.mobileHeight,
  507 + mobileHide: layout.mobileHide
506 508 },
507 509 {emitEvent: false}
508 510 );
... ... @@ -510,7 +512,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
510 512 this.layoutSettings.patchValue(
511 513 {
512 514 mobileOrder: null,
513   - mobileHeight: null
  515 + mobileHeight: null,
  516 + mobileHide: false
514 517 },
515 518 {emitEvent: false}
516 519 );
... ...
... ... @@ -63,6 +63,7 @@ export interface IDashboardComponent {
63 63 dashboardWidgets: DashboardWidgets;
64 64 mobileAutofillHeight: boolean;
65 65 isMobileSize: boolean;
  66 + isEdit: boolean;
66 67 autofillHeight: boolean;
67 68 dashboardTimewindow: Timewindow;
68 69 dashboardTimewindowChanged: Observable<Timewindow>;
... ... @@ -99,7 +100,14 @@ export class DashboardWidgets implements Iterable<DashboardWidget> {
99 100 widgetLayouts: WidgetLayouts;
100 101
101 102 [Symbol.iterator](): Iterator<DashboardWidget> {
102   - return this.dashboardWidgets[Symbol.iterator]();
  103 + return this.activeDashboardWidgets[Symbol.iterator]();
  104 + }
  105 +
  106 + get activeDashboardWidgets(): Array<DashboardWidget> {
  107 + if (this.dashboard.isMobileSize && !this.dashboard.isEdit) {
  108 + return this.dashboardWidgets.filter(w => !w.mobileHide);
  109 + }
  110 + return this.dashboardWidgets;
103 111 }
104 112
105 113 constructor(private dashboard: IDashboardComponent,
... ... @@ -152,6 +160,7 @@ export class DashboardWidgets implements Iterable<DashboardWidget> {
152 160 }
153 161 if (updateRecords.length) {
154 162 updateRecords.forEach((record) => {
  163 + let index;
155 164 switch (record.operation) {
156 165 case 'add':
157 166 this.dashboardWidgets.push(
... ... @@ -159,7 +168,7 @@ export class DashboardWidgets implements Iterable<DashboardWidget> {
159 168 );
160 169 break;
161 170 case 'remove':
162   - let index = this.dashboardWidgets.findIndex((dashboardWidget) => dashboardWidget.widgetId === record.widgetId);
  171 + index = this.dashboardWidgets.findIndex((dashboardWidget) => dashboardWidget.widgetId === record.widgetId);
163 172 if (index > -1) {
164 173 this.dashboardWidgets.splice(index, 1);
165 174 }
... ... @@ -261,7 +270,7 @@ export class DashboardWidgets implements Iterable<DashboardWidget> {
261 270
262 271 private updateRowsAndSort() {
263 272 let maxRows = this.dashboard.gridsterOpts.maxRows;
264   - this.dashboardWidgets.forEach((dashboardWidget) => {
  273 + this.activeDashboardWidgets.forEach((dashboardWidget) => {
265 274 const bottom = dashboardWidget.y + dashboardWidget.rows;
266 275 maxRows = Math.max(maxRows, bottom);
267 276 });
... ... @@ -328,6 +337,10 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
328 337 private gridsterItemComponentSubject = new Subject<GridsterItemComponentInterface>();
329 338 private gridsterItemComponentValue: GridsterItemComponentInterface;
330 339
  340 + get mobileHide(): boolean {
  341 + return this.widgetLayout ? this.widgetLayout.mobileHide === true : false;
  342 + }
  343 +
331 344 set gridsterItemComponent(item: GridsterItemComponentInterface) {
332 345 this.gridsterItemComponentValue = item;
333 346 this.gridsterItemComponentSubject.next(this.gridsterItemComponentValue);
... ... @@ -423,7 +436,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
423 436 this.showWidgetTitlePanel = this.widgetContext.hideTitlePanel ? false :
424 437 this.showTitle || this.hasTimewindow;
425 438
426   - this.showWidgetActions = this.widgetContext.hideTitlePanel ? false : true;
  439 + this.showWidgetActions = !this.widgetContext.hideTitlePanel;
427 440
428 441 this.customHeaderActions = this.widgetContext.customHeaderActions ? this.widgetContext.customHeaderActions : [];
429 442 this.widgetActions = this.widgetContext.widgetActions ? this.widgetContext.widgetActions : [];
... ...
... ... @@ -105,17 +105,25 @@
105 105 {{ 'dashboard.title-required' | translate }}
106 106 </mat-error>
107 107 </mat-form-field>
108   - <tb-image-input fxFlex
109   - label="{{'dashboard.image' | translate}}"
110   - maxSizeByte="524288"
111   - formControlName="image">
112   - </tb-image-input>
113 108 <div formGroupName="configuration" fxLayout="column">
114 109 <mat-form-field class="mat-block">
115 110 <mat-label translate>dashboard.description</mat-label>
116 111 <textarea matInput formControlName="description" rows="2"></textarea>
117 112 </mat-form-field>
118 113 </div>
  114 + <div style="padding-bottom: 16px;" translate>dashboard.mobile-app-settings</div>
  115 + <tb-image-input fxFlex
  116 + label="{{'dashboard.image' | translate}}"
  117 + maxSizeByte="524288"
  118 + formControlName="image">
  119 + </tb-image-input>
  120 + <mat-checkbox fxFlex formControlName="mobileHide" style="padding-bottom: 16px; padding-top: 16px;">
  121 + {{ 'dashboard.mobile-hide' | translate }}
  122 + </mat-checkbox>
  123 + <mat-form-field class="mat-block">
  124 + <mat-label translate>dashboard.mobile-order</mat-label>
  125 + <input matInput formControlName="mobileOrder" type="number" step="1">
  126 + </mat-form-field>
119 127 </fieldset>
120 128 </form>
121 129 </div>
... ...
... ... @@ -81,6 +81,8 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> {
81 81 {
82 82 title: [entity ? entity.title : '', [Validators.required]],
83 83 image: [entity ? entity.image : null],
  84 + mobileHide: [entity ? entity.mobileHide : false],
  85 + mobileOrder: [entity ? entity.mobileOrder : null, [Validators.pattern(/^-?[0-9]+$/)]],
84 86 configuration: this.fb.group(
85 87 {
86 88 description: [entity && entity.configuration ? entity.configuration.description : ''],
... ... @@ -94,6 +96,8 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> {
94 96 this.updateFields(entity);
95 97 this.entityForm.patchValue({title: entity.title});
96 98 this.entityForm.patchValue({image: entity.image});
  99 + this.entityForm.patchValue({mobileHide: entity.mobileHide});
  100 + this.entityForm.patchValue({mobileOrder: entity.mobileOrder});
97 101 this.entityForm.patchValue({configuration: {description: entity.configuration ? entity.configuration.description : ''}});
98 102 }
99 103
... ...
... ... @@ -28,11 +28,14 @@ export interface DashboardInfo extends BaseData<DashboardId> {
28 28 title?: string;
29 29 image?: string;
30 30 assignedCustomers?: Array<ShortCustomerInfo>;
  31 + mobileHide?: boolean;
  32 + mobileOrder?: number;
31 33 }
32 34
33 35 export interface WidgetLayout {
34 36 sizeX?: number;
35 37 sizeY?: number;
  38 + mobileHide?: boolean;
36 39 mobileHeight?: number;
37 40 mobileOrder?: number;
38 41 col?: number;
... ...
... ... @@ -484,6 +484,7 @@ export interface WidgetConfig {
484 484 showLegend?: boolean;
485 485 legendConfig?: LegendConfig;
486 486 timewindow?: Timewindow;
  487 + mobileHide?: boolean;
487 488 mobileHeight?: number;
488 489 mobileOrder?: number;
489 490 color?: string;
... ...
... ... @@ -694,6 +694,9 @@
694 694 "add-widget": "Add new widget",
695 695 "title": "Title",
696 696 "image": "Dashboard image",
  697 + "mobile-app-settings": "Mobile application settings",
  698 + "mobile-order": "Dashboard order in mobile application",
  699 + "mobile-hide": "Hide dashboard in mobile application",
697 700 "update-image": "Update dashboard image",
698 701 "take-screenshot": "Take screenshot",
699 702 "select-widget-title": "Select widget",
... ... @@ -2970,6 +2973,7 @@
2970 2973 "mobile-mode-settings": "Mobile mode settings",
2971 2974 "order": "Order",
2972 2975 "height": "Height",
  2976 + "mobile-hide": "Hide widget in mobile mode",
2973 2977 "units": "Special symbol to show next to value",
2974 2978 "decimals": "Number of digits after floating point",
2975 2979 "timewindow": "Timewindow",
... ...