From dcbc4c5c7d5ccb040fb33d7a6a8ee30b957986a7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 27 Apr 2023 19:09:56 +0300 Subject: [PATCH 1/5] Device Info Filter --- .../main/data/upgrade/3.4.4/schema_update.sql | 15 +++ .../controller/ControllerConstants.java | 1 + .../server/controller/DeviceController.java | 29 +++-- .../server/dao/device/DeviceService.java | 13 +- .../server/common/data/DeviceInfo.java | 5 +- .../server/common/data/DeviceInfoFilter.java | 34 ++++++ .../org/thingsboard/server/dao/DaoUtil.java | 36 ++++++ .../server/dao/device/DeviceDao.java | 64 +--------- .../server/dao/device/DeviceServiceImpl.java | 62 ++-------- .../server/dao/model/ModelConstants.java | 8 ++ .../thingsboard/server/dao/model/ToData.java | 4 + .../dao/model/sql/DeviceInfoEntity.java | 47 +++++--- .../server/dao/sql/alarm/JpaAlarmDao.java | 28 +---- .../dao/sql/device/DeviceRepository.java | 114 +++++------------- .../server/dao/sql/device/JpaDeviceDao.java | 84 +++++-------- .../main/resources/sql/schema-entities.sql | 10 ++ .../dao/service/BaseDeviceServiceTest.java | 33 ++--- 17 files changed, 253 insertions(+), 334 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfoFilter.java diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index a248766c7e..be3d70b163 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -14,6 +14,21 @@ -- limitations under the License. -- +-- DEVICE INFO VIEW START + +DROP VIEW IF EXISTS device_info_view CASCADE; +CREATE OR REPLACE VIEW device_info_view as +SELECT d.* + , c.title as customer_title + , c.additional_info::json->>'isPublic' as customer_is_public + , d.type as device_profile_name + , (select bool_v from attribute_kv da where da.entity_id = d.id and da.attribute_key = 'active') as attribute_active + , (select bool_v from ts_kv_latest dt where dt.entity_id = d.id and dt.key = (select key_id from ts_kv_dictionary where key = 'active')) as ts_active +FROM device d + LEFT JOIN customer c ON c.id = d.customer_id; + +-- DEVICE INFO VIEW END + -- USER CREDENTIALS START ALTER TABLE user_credentials diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index a060335713..f425bde86c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -66,6 +66,7 @@ public class ControllerConstants { protected static final String PAGE_SIZE_DESCRIPTION = "Maximum amount of entities in a one page"; protected static final String PAGE_NUMBER_DESCRIPTION = "Sequence number of page starting from 0"; protected static final String DEVICE_TYPE_DESCRIPTION = "Device type as the name of the device profile"; + protected static final String DEVICE_ACTIVE_PARAM_DESCRIPTION = "A boolean value representing the device active flag."; protected static final String ENTITY_VIEW_TYPE_DESCRIPTION = "Entity View type"; protected static final String ASSET_TYPE_DESCRIPTION = "Asset type"; protected static final String EDGE_TYPE_DESCRIPTION = "A string value representing the edge type. For example, 'default'"; diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 0e6e19ad8d..d68aed4284 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceInfoFilter; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; import org.thingsboard.server.common.data.Tenant; @@ -83,6 +84,7 @@ import java.util.stream.Collectors; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ACTIVE_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_INFO_DESCRIPTION; @@ -333,6 +335,8 @@ public class DeviceController extends BaseController { @RequestParam(required = false) String type, @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @RequestParam(required = false) String deviceProfileId, + @ApiParam(value = DEVICE_ACTIVE_PARAM_DESCRIPTION) + @RequestParam(required = false) Boolean active, @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES) @@ -342,14 +346,15 @@ public class DeviceController extends BaseController { ) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + DeviceInfoFilter.DeviceInfoFilterBuilder filter = DeviceInfoFilter.builder(); + filter.tenantId(tenantId); + filter.active(active); if (type != null && type.trim().length() > 0) { - return checkNotNull(deviceService.findDeviceInfosByTenantIdAndType(tenantId, type, pageLink)); + filter.type(type); } else if (deviceProfileId != null && deviceProfileId.length() > 0) { - DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId)); - return checkNotNull(deviceService.findDeviceInfosByTenantIdAndDeviceProfileId(tenantId, profileId, pageLink)); - } else { - return checkNotNull(deviceService.findDeviceInfosByTenantId(tenantId, pageLink)); + filter.deviceProfileId(new DeviceProfileId(toUUID(deviceProfileId))); } + return checkNotNull(deviceService.findDeviceInfosByFilter(filter.build(), pageLink)); } @ApiOperation(value = "Get Tenant Device (getTenantDevice)", @@ -415,6 +420,8 @@ public class DeviceController extends BaseController { @RequestParam(required = false) String type, @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) @RequestParam(required = false) String deviceProfileId, + @ApiParam(value = DEVICE_ACTIVE_PARAM_DESCRIPTION) + @RequestParam(required = false) Boolean active, @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES) @@ -426,14 +433,16 @@ public class DeviceController extends BaseController { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); checkCustomerId(customerId, Operation.READ); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + DeviceInfoFilter.DeviceInfoFilterBuilder filter = DeviceInfoFilter.builder(); + filter.tenantId(tenantId); + filter.customerId(customerId); + filter.active(active); if (type != null && type.trim().length() > 0) { - return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); + filter.type(type); } else if (deviceProfileId != null && deviceProfileId.length() > 0) { - DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId)); - return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(tenantId, customerId, profileId, pageLink)); - } else { - return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink)); + filter.deviceProfileId(new DeviceProfileId(toUUID(deviceProfileId))); } + return checkNotNull(deviceService.findDeviceInfosByFilter(filter.build(), pageLink)); } @ApiOperation(value = "Get Devices By Ids (getDevicesByIds)", diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index 4383ccc0a7..a90ea9a572 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceInfoFilter; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; @@ -66,7 +67,7 @@ public interface DeviceService extends EntityDaoService { PageData findDevicesByTenantId(TenantId tenantId, PageLink pageLink); - PageData findDeviceInfosByTenantId(TenantId tenantId, PageLink pageLink); + PageData findDeviceInfosByFilter(DeviceInfoFilter filter, PageLink pageLink); PageData findDeviceIdInfos(PageLink pageLink); @@ -76,10 +77,6 @@ public interface DeviceService extends EntityDaoService { Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType); - PageData findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); - - PageData findDeviceInfosByTenantIdAndDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId, PageLink pageLink); - ListenableFuture> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List deviceIds); List findDevicesByIds(List deviceIds); @@ -90,14 +87,8 @@ public interface DeviceService extends EntityDaoService { PageData findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); - PageData findDeviceInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); - PageData findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink); - PageData findDeviceInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink); - - PageData findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(TenantId tenantId, CustomerId customerId, DeviceProfileId deviceProfileId, PageLink pageLink); - ListenableFuture> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List deviceIds); void unassignCustomerDevices(TenantId tenantId, CustomerId customerId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java index ee11726923..c1ffbeb79e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java @@ -30,6 +30,8 @@ public class DeviceInfo extends Device { private boolean customerIsPublic; @ApiModelProperty(position = 15, value = "Name of the corresponding Device Profile.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String deviceProfileName; + @ApiModelProperty(position = 16, value = "Device active flag.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + private boolean active; public DeviceInfo() { super(); @@ -39,10 +41,11 @@ public class DeviceInfo extends Device { super(deviceId); } - public DeviceInfo(Device device, String customerTitle, boolean customerIsPublic, String deviceProfileName) { + public DeviceInfo(Device device, String customerTitle, boolean customerIsPublic, String deviceProfileName, boolean active) { super(device); this.customerTitle = customerTitle; this.customerIsPublic = customerIsPublic; this.deviceProfileName = deviceProfileName; + this.active = active; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfoFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfoFilter.java new file mode 100644 index 0000000000..4d5f6e0006 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfoFilter.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2023 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.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; + +@Data +@Builder +public class DeviceInfoFilter { + + private TenantId tenantId; + private CustomerId customerId; + private String type; + private DeviceProfileId deviceProfileId; + private Boolean active; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java index 5c45e4d44b..95f3eab209 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.page.PageData; @@ -45,6 +46,12 @@ public abstract class DaoUtil { return new PageData<>(data, page.getTotalPages(), page.getTotalElements(), page.hasNext()); } + public static PageData toPageData(Page> page, Object... params) { + List data = convertDataList(page.getContent(), params); + return new PageData<>(data, page.getTotalPages(), page.getTotalElements(), page.hasNext()); + } + + public static PageData pageToPageData(Page page) { return new PageData<>(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.hasNext()); } @@ -78,6 +85,19 @@ public abstract class DaoUtil { return list; } + public static List convertDataList(Collection> toDataList, Object... params) { + List list = Collections.emptyList(); + if (toDataList != null && !toDataList.isEmpty()) { + list = new ArrayList<>(); + for (ToData object : toDataList) { + if (object != null) { + list.add(object.toData(params)); + } + } + } + return list; + } + public static T getData(ToData data) { T object = null; if (data != null) { @@ -86,6 +106,15 @@ public abstract class DaoUtil { return object; } + public static T getData(ToData data, Object... params) { + T object = null; + if (data != null) { + object = data.toData(params); + } + return object; + } + + public static T getData(Optional> data) { T object = null; if (data.isPresent()) { @@ -128,4 +157,11 @@ public abstract class DaoUtil { } while (hasNextBatch); } + public static String getStringId(UUIDBased id) { + if (id != null) { + return id.toString(); + } else { + return null; + } + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index ecdeb9e90f..f89541c77a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.device; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceInfoFilter; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.DeviceIdInfo; @@ -74,15 +75,6 @@ public interface DeviceDao extends Dao, TenantEntityDao, ExportableEntit */ PageData findDevicesByTenantId(UUID tenantId, PageLink pageLink); - /** - * Find device infos by tenantId and page link. - * - * @param tenantId the tenantId - * @param pageLink the page link - * @return the list of device info objects - */ - PageData findDeviceInfosByTenantId(UUID tenantId, PageLink pageLink); - /** * Find devices by tenantId, type and page link. * @@ -100,26 +92,6 @@ public interface DeviceDao extends Dao, TenantEntityDao, ExportableEntit Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(UUID tenantId, UUID deviceProfileId, OtaPackageType otaPackageType); - /** - * Find device infos by tenantId, type and page link. - * - * @param tenantId the tenantId - * @param type the type - * @param pageLink the page link - * @return the list of device info objects - */ - PageData findDeviceInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); - - /** - * Find device infos by tenantId, deviceProfileId and page link. - * - * @param tenantId the tenantId - * @param deviceProfileId the deviceProfileId - * @param pageLink the page link - * @return the list of device info objects - */ - PageData findDeviceInfosByTenantIdAndDeviceProfileId(UUID tenantId, UUID deviceProfileId, PageLink pageLink); - /** * Find devices by tenantId and devices Ids. * @@ -155,16 +127,6 @@ public interface DeviceDao extends Dao, TenantEntityDao, ExportableEntit */ PageData findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); - /** - * Find device infos by tenantId, customerId and page link. - * - * @param tenantId the tenantId - * @param customerId the customerId - * @param pageLink the page link - * @return the list of device info objects - */ - PageData findDeviceInfosByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); - /** * Find devices by tenantId, customerId, type and page link. * @@ -176,28 +138,6 @@ public interface DeviceDao extends Dao, TenantEntityDao, ExportableEntit */ PageData findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink); - /** - * Find device infos by tenantId, customerId, type and page link. - * - * @param tenantId the tenantId - * @param customerId the customerId - * @param type the type - * @param pageLink the page link - * @return the list of device info objects - */ - PageData findDeviceInfosByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink); - - /** - * Find device infos by tenantId, customerId, deviceProfileId and page link. - * - * @param tenantId the tenantId - * @param customerId the customerId - * @param deviceProfileId the deviceProfileId - * @param pageLink the page link - * @return the list of device info objects - */ - PageData findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(UUID tenantId, UUID customerId, UUID deviceProfileId, PageLink pageLink); - /** * Find devices by tenantId, customerId and devices Ids. * @@ -277,5 +217,5 @@ public interface DeviceDao extends Dao, TenantEntityDao, ExportableEntit PageData findDeviceIdInfos(PageLink pageLink); - + PageData findDeviceInfosByFilter(DeviceInfoFilter filter, PageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index b9bcd83e93..a662f50dca 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -30,6 +30,7 @@ import org.thingsboard.server.cache.device.DeviceCacheKey; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceInfoFilter; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; @@ -69,6 +70,7 @@ import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.entity.EntityCountService; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -342,12 +344,17 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDeviceInfosByTenantId(TenantId tenantId, PageLink pageLink) { - log.trace("Executing findDeviceInfosByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + public PageData findDeviceInfosByFilter(DeviceInfoFilter filter, PageLink pageLink) { + log.trace("Executing findDeviceInfosByFilter, filter [{}], pageLink [{}]", filter, pageLink); + if (filter == null) { + throw new IncorrectParameterException("Filter is empty!"); + } + validateId(filter.getTenantId(), INCORRECT_TENANT_ID + filter.getTenantId()); validatePageLink(pageLink); - return deviceDao.findDeviceInfosByTenantId(tenantId.getId(), pageLink); + return deviceDao.findDeviceInfosByFilter(filter, pageLink); + } @Override @@ -387,24 +394,6 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { - log.trace("Executing findDeviceInfosByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validateString(type, "Incorrect type " + type); - validatePageLink(pageLink); - return deviceDao.findDeviceInfosByTenantIdAndType(tenantId.getId(), type, pageLink); - } - - @Override - public PageData findDeviceInfosByTenantIdAndDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId, PageLink pageLink) { - log.trace("Executing findDeviceInfosByTenantIdAndDeviceProfileId, tenantId [{}], deviceProfileId [{}], pageLink [{}]", tenantId, deviceProfileId, pageLink); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validateId(deviceProfileId, INCORRECT_DEVICE_PROFILE_ID + deviceProfileId); - validatePageLink(pageLink); - return deviceDao.findDeviceInfosByTenantIdAndDeviceProfileId(tenantId.getId(), deviceProfileId.getId(), pageLink); - } - @Override public ListenableFuture> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List deviceIds) { log.trace("Executing findDevicesByTenantIdAndIdsAsync, tenantId [{}], deviceIds [{}]", tenantId, deviceIds); @@ -444,15 +433,6 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDeviceInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) { - log.trace("Executing findDeviceInfosByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); - validatePageLink(pageLink); - return deviceDao.findDeviceInfosByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); - } - @Override public PageData findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { log.trace("Executing findDevicesByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); @@ -463,26 +443,6 @@ public class DeviceServiceImpl extends AbstractCachedEntityService findDeviceInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { - log.trace("Executing findDeviceInfosByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); - validateString(type, "Incorrect type " + type); - validatePageLink(pageLink); - return deviceDao.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink); - } - - @Override - public PageData findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(TenantId tenantId, CustomerId customerId, DeviceProfileId deviceProfileId, PageLink pageLink) { - log.trace("Executing findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId, tenantId [{}], customerId [{}], deviceProfileId [{}], pageLink [{}]", tenantId, customerId, deviceProfileId, pageLink); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); - validateId(deviceProfileId, INCORRECT_DEVICE_PROFILE_ID + deviceProfileId); - validatePageLink(pageLink); - return deviceDao.findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(tenantId.getId(), customerId.getId(), deviceProfileId.getId(), pageLink); - } - @Override public ListenableFuture> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List deviceIds) { log.trace("Executing findDevicesByTenantIdCustomerIdAndIdsAsync, tenantId [{}], customerId [{}], deviceIds [{}]", tenantId, customerId, deviceIds); 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 bcbed91d2a..41d6685fd8 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 @@ -169,6 +169,12 @@ public class ModelConstants { public static final String DEVICE_FIRMWARE_ID_PROPERTY = "firmware_id"; public static final String DEVICE_SOFTWARE_ID_PROPERTY = "software_id"; + public static final String DEVICE_CUSTOMER_TITLE_PROPERTY = "customer_title"; + public static final String DEVICE_CUSTOMER_IS_PUBLIC_PROPERTY = "customer_is_public"; + public static final String DEVICE_DEVICE_PROFILE_NAME_PROPERTY = "device_profile_name"; + public static final String DEVICE_ATTR_ACTIVE_PROPERTY = "attribute_active"; + public static final String DEVICE_TS_ACTIVE_PROPERTY = "ts_active"; + public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text"; public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text"; public static final String DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_and_search_text"; @@ -176,6 +182,8 @@ public class ModelConstants { public static final String DEVICE_BY_TENANT_AND_NAME_VIEW_NAME = "device_by_tenant_and_name"; public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant"; + public static final String DEVICE_INFO_VIEW_COLUMN_FAMILY_NAME = "device_info_view"; + /** * Device profile constants. */ diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ToData.java b/dao/src/main/java/org/thingsboard/server/dao/model/ToData.java index 3c3e5ac8c4..0e69506660 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ToData.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ToData.java @@ -28,4 +28,8 @@ public interface ToData { * @return the dto object */ T toData(); + + default T toData(Object... params) { + return toData(); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java index ea2d27bf4b..40dba47048 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java @@ -15,48 +15,57 @@ */ package org.thingsboard.server.dao.model.sql; -import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Immutable; import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.dao.model.ModelConstants; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; import java.util.HashMap; import java.util.Map; @Data @EqualsAndHashCode(callSuper = true) +@Entity +@Immutable +@Table(name = ModelConstants.DEVICE_INFO_VIEW_COLUMN_FAMILY_NAME) public class DeviceInfoEntity extends AbstractDeviceEntity { - public static final Map deviceInfoColumnMap = new HashMap<>(); + public static final Map attrActiveColumnMap = new HashMap<>(); + public static final Map tsActiveColumnMap = new HashMap<>(); static { - deviceInfoColumnMap.put("customerTitle", "c.title"); - deviceInfoColumnMap.put("deviceProfileName", "p.name"); + attrActiveColumnMap.put("active", "attributeActive"); + tsActiveColumnMap.put("active", "tsActive"); } + @Column(name = ModelConstants.DEVICE_CUSTOMER_TITLE_PROPERTY) private String customerTitle; - private boolean customerIsPublic; + @Column(name = ModelConstants.DEVICE_CUSTOMER_IS_PUBLIC_PROPERTY) + private Boolean customerIsPublic; + @Column(name = ModelConstants.DEVICE_DEVICE_PROFILE_NAME_PROPERTY) private String deviceProfileName; + @Column(name = ModelConstants.DEVICE_ATTR_ACTIVE_PROPERTY) + private Boolean attributeActive; + @Column(name = ModelConstants.DEVICE_TS_ACTIVE_PROPERTY) + private Boolean tsActive; public DeviceInfoEntity() { super(); } - public DeviceInfoEntity(DeviceEntity deviceEntity, - String customerTitle, - Object customerAdditionalInfo, - String deviceProfileName) { - super(deviceEntity); - this.customerTitle = customerTitle; - if (customerAdditionalInfo != null && ((JsonNode)customerAdditionalInfo).has("isPublic")) { - this.customerIsPublic = ((JsonNode)customerAdditionalInfo).get("isPublic").asBoolean(); - } else { - this.customerIsPublic = false; - } - this.deviceProfileName = deviceProfileName; - } @Override public DeviceInfo toData() { - return new DeviceInfo(super.toDevice(), customerTitle, customerIsPublic, deviceProfileName); + return toData(false); + } + + @Override + public DeviceInfo toData(Object... persistToTelemetry) { + boolean attr = persistToTelemetry.length == 0 || !(Boolean) persistToTelemetry[0]; + return new DeviceInfo(super.toDevice(), customerTitle, Boolean.TRUE.equals(customerIsPublic), deviceProfileName, + attr ? Boolean.TRUE.equals(attributeActive) : Boolean.TRUE.equals(tsActive)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index 98d607735e..93c079360c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -137,10 +137,6 @@ public class JpaAlarmDao extends JpaAbstractDao implements A log.trace("Try to find alarms by entity [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getStatus(), query.getPageLink()); EntityId affectedEntity = query.getAffectedEntityId(); AlarmStatusFilter asf = AlarmStatusFilter.from(query); - String assigneeId = null; - if (query.getAssigneeId() != null) { - assigneeId = query.getAssigneeId().toString(); - } if (affectedEntity != null) { return DaoUtil.toPageData( alarmRepository.findAlarms( @@ -153,7 +149,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A asf.hasClearFilter() && asf.getClearFilter(), asf.hasAckFilter(), asf.hasAckFilter() && asf.getAckFilter(), - assigneeId, + DaoUtil.getStringId(query.getAssigneeId()), Objects.toString(query.getPageLink().getTextSearch(), ""), DaoUtil.toPageable(query.getPageLink()) ) @@ -168,7 +164,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A asf.hasClearFilter() && asf.getClearFilter(), asf.hasAckFilter(), asf.hasAckFilter() && asf.getAckFilter(), - assigneeId, + DaoUtil.getStringId(query.getAssigneeId()), Objects.toString(query.getPageLink().getTextSearch(), ""), DaoUtil.toPageable(query.getPageLink()) ) @@ -180,10 +176,6 @@ public class JpaAlarmDao extends JpaAbstractDao implements A public PageData findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) { log.trace("Try to find customer alarms by status [{}] and pageLink [{}]", query.getStatus(), query.getPageLink()); AlarmStatusFilter asf = AlarmStatusFilter.from(query); - String assigneeId = null; - if (query.getAssigneeId() != null) { - assigneeId = query.getAssigneeId().toString(); - } return DaoUtil.toPageData( alarmRepository.findCustomerAlarms( tenantId.getId(), @@ -194,7 +186,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A asf.hasClearFilter() && asf.getClearFilter(), asf.hasAckFilter(), asf.hasAckFilter() && asf.getAckFilter(), - assigneeId, + DaoUtil.getStringId(query.getAssigneeId()), Objects.toString(query.getPageLink().getTextSearch(), ""), DaoUtil.toPageable(query.getPageLink()) ) @@ -208,10 +200,6 @@ public class JpaAlarmDao extends JpaAbstractDao implements A List typeList = query.getTypeList() != null && !query.getTypeList().isEmpty() ? query.getTypeList() : null; List severityList = query.getSeverityList() != null && !query.getSeverityList().isEmpty() ? query.getSeverityList() : null; AlarmStatusFilter asf = AlarmStatusFilter.from(query.getStatusList()); - String assigneeId = null; - if (query.getAssigneeId() != null) { - assigneeId = query.getAssigneeId().toString(); - } if (affectedEntity != null) { return DaoUtil.toPageData( alarmRepository.findAlarmsV2( @@ -226,7 +214,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A asf.hasClearFilter() && asf.getClearFilter(), asf.hasAckFilter(), asf.hasAckFilter() && asf.getAckFilter(), - assigneeId, + DaoUtil.getStringId(query.getAssigneeId()), Objects.toString(query.getPageLink().getTextSearch(), ""), DaoUtil.toPageable(query.getPageLink()) ) @@ -243,7 +231,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A asf.hasClearFilter() && asf.getClearFilter(), asf.hasAckFilter(), asf.hasAckFilter() && asf.getAckFilter(), - assigneeId, + DaoUtil.getStringId(query.getAssigneeId()), Objects.toString(query.getPageLink().getTextSearch(), ""), DaoUtil.toPageable(query.getPageLink()) ) @@ -257,10 +245,6 @@ public class JpaAlarmDao extends JpaAbstractDao implements A List typeList = query.getTypeList() != null && !query.getTypeList().isEmpty() ? query.getTypeList() : null; List severityList = query.getSeverityList() != null && !query.getSeverityList().isEmpty() ? query.getSeverityList() : null; AlarmStatusFilter asf = AlarmStatusFilter.from(query.getStatusList()); - String assigneeId = null; - if (query.getAssigneeId() != null) { - assigneeId = query.getAssigneeId().toString(); - } return DaoUtil.toPageData( alarmRepository.findCustomerAlarmsV2( tenantId.getId(), @@ -273,7 +257,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A asf.hasClearFilter() && asf.getClearFilter(), asf.hasAckFilter(), asf.hasAckFilter() && asf.getAckFilter(), - assigneeId, + DaoUtil.getStringId(query.getAssigneeId()), Objects.toString(query.getPageLink().getTextSearch(), ""), DaoUtil.toPageable(query.getPageLink()) ) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index cf711e43a0..ac411a580b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -22,6 +22,9 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceIdInfo; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.ExportableEntityRepository; import org.thingsboard.server.dao.model.sql.DeviceEntity; import org.thingsboard.server.dao.model.sql.DeviceInfoEntity; @@ -29,16 +32,9 @@ import org.thingsboard.server.dao.model.sql.DeviceInfoEntity; import java.util.List; import java.util.UUID; -/** - * Created by Valerii Sosliuk on 5/6/2017. - */ public interface DeviceRepository extends JpaRepository, ExportableEntityRepository { - @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + - "FROM DeviceEntity d " + - "LEFT JOIN CustomerEntity c on c.id = d.customerId " + - "LEFT JOIN DeviceProfileEntity p on p.id = d.deviceProfileId " + - "WHERE d.id = :deviceId") + @Query("SELECT d FROM DeviceInfoEntity d WHERE d.id = :deviceId") DeviceInfoEntity findDeviceInfoById(@Param("deviceId") UUID deviceId); @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + @@ -57,10 +53,7 @@ public interface DeviceRepository extends JpaRepository, Exp @Param("searchText") String searchText, Pageable pageable); - @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + - "FROM DeviceEntity d " + - "LEFT JOIN CustomerEntity c on c.id = d.customerId " + - "LEFT JOIN DeviceProfileEntity p on p.id = d.deviceProfileId " + + @Query("SELECT d FROM DeviceInfoEntity d " + "WHERE d.tenantId = :tenantId " + "AND d.customerId = :customerId " + "AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") @@ -79,19 +72,6 @@ public interface DeviceRepository extends JpaRepository, Exp @Param("textSearch") String textSearch, Pageable pageable); - @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + - "FROM DeviceEntity d " + - "LEFT JOIN CustomerEntity c on c.id = d.customerId " + - "LEFT JOIN DeviceProfileEntity p on p.id = d.deviceProfileId " + - "WHERE d.tenantId = :tenantId " + - "AND (LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + - "OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + - "OR LOWER(p.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + - "OR LOWER(c.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')))") - Page findDeviceInfosByTenantId(@Param("tenantId") UUID tenantId, - @Param("textSearch") String textSearch, - Pageable pageable); - @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND d.type = :type " + "AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") @@ -105,9 +85,9 @@ public interface DeviceRepository extends JpaRepository, Exp "AND d.firmwareId = null " + "AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") Page findByTenantIdAndTypeAndFirmwareIdIsNull(@Param("tenantId") UUID tenantId, - @Param("deviceProfileId") UUID deviceProfileId, - @Param("textSearch") String textSearch, - Pageable pageable); + @Param("deviceProfileId") UUID deviceProfileId, + @Param("textSearch") String textSearch, + Pageable pageable); @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND d.deviceProfileId = :deviceProfileId " + @@ -130,34 +110,6 @@ public interface DeviceRepository extends JpaRepository, Exp Long countByTenantIdAndDeviceProfileIdAndSoftwareIdIsNull(@Param("tenantId") UUID tenantId, @Param("deviceProfileId") UUID deviceProfileId); - @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + - "FROM DeviceEntity d " + - "LEFT JOIN CustomerEntity c on c.id = d.customerId " + - "LEFT JOIN DeviceProfileEntity p on p.id = d.deviceProfileId " + - "WHERE d.tenantId = :tenantId " + - "AND d.type = :type " + - "AND (LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + - "OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + - "OR LOWER(c.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')))") - Page findDeviceInfosByTenantIdAndType(@Param("tenantId") UUID tenantId, - @Param("type") String type, - @Param("textSearch") String textSearch, - Pageable pageable); - - @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + - "FROM DeviceEntity d " + - "LEFT JOIN CustomerEntity c on c.id = d.customerId " + - "LEFT JOIN DeviceProfileEntity p on p.id = d.deviceProfileId " + - "WHERE d.tenantId = :tenantId " + - "AND d.deviceProfileId = :deviceProfileId " + - "AND (LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + - "OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + - "OR LOWER(c.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')))") - Page findDeviceInfosByTenantIdAndDeviceProfileId(@Param("tenantId") UUID tenantId, - @Param("deviceProfileId") UUID deviceProfileId, - @Param("textSearch") String textSearch, - Pageable pageable); - @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND d.customerId = :customerId " + "AND d.type = :type " + @@ -168,33 +120,25 @@ public interface DeviceRepository extends JpaRepository, Exp @Param("textSearch") String textSearch, Pageable pageable); - @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + - "FROM DeviceEntity d " + - "LEFT JOIN CustomerEntity c on c.id = d.customerId " + - "LEFT JOIN DeviceProfileEntity p on p.id = d.deviceProfileId " + + @Query("SELECT d FROM DeviceInfoEntity d " + "WHERE d.tenantId = :tenantId " + - "AND d.customerId = :customerId " + - "AND d.type = :type " + - "AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") - Page findDeviceInfosByTenantIdAndCustomerIdAndType(@Param("tenantId") UUID tenantId, - @Param("customerId") UUID customerId, - @Param("type") String type, - @Param("textSearch") String textSearch, - Pageable pageable); - - @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + - "FROM DeviceEntity d " + - "LEFT JOIN CustomerEntity c on c.id = d.customerId " + - "LEFT JOIN DeviceProfileEntity p on p.id = d.deviceProfileId " + - "WHERE d.tenantId = :tenantId " + - "AND d.customerId = :customerId " + - "AND d.deviceProfileId = :deviceProfileId " + - "AND LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") - Page findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(@Param("tenantId") UUID tenantId, - @Param("customerId") UUID customerId, - @Param("deviceProfileId") UUID deviceProfileId, - @Param("textSearch") String textSearch, - Pageable pageable); + "AND (:customerId IS NULL OR d.customerId = uuid(:customerId)) " + + "AND ((:deviceType) IS NULL OR d.type = :deviceType) " + + "AND (:deviceProfileId IS NULL OR d.deviceProfileId = uuid(:deviceProfileId)) " + + "AND ((:filterByActive) IS FALSE OR (((:persistToTelemetry) IS TRUE AND d.tsActive = :deviceActive) OR ((:persistToTelemetry) IS FALSE AND d.attributeActive = :deviceActive))) " + + "AND (LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + + "OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + + "OR LOWER(d.type) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + + "OR LOWER(d.customerTitle) LIKE LOWER(CONCAT('%', :textSearch, '%')))") + Page findDeviceInfosByFilter(@Param("tenantId") UUID tenantId, + @Param("customerId") String customerId, + @Param("deviceType") String type, + @Param("deviceProfileId") String deviceProfileId, + @Param("filterByActive") boolean filterByActive, + @Param("persistToTelemetry") boolean persistToTelemetry, + @Param("deviceActive") boolean active, + @Param("textSearch") String textSearch, + Pageable pageable); @Query("SELECT DISTINCT d.type FROM DeviceEntity d WHERE d.tenantId = :tenantId") List findTenantDeviceTypes(@Param("tenantId") UUID tenantId); @@ -237,10 +181,10 @@ public interface DeviceRepository extends JpaRepository, Exp *

* There is two way to count devices. * OPTIMAL: count(*) - * - returns _row_count_ and use index-only scan (super fast). + * - returns _row_count_ and use index-only scan (super fast). * SLOW: count(id) - * - returns _NON_NULL_id_count and performs table scan to verify isNull for each id in filtered rows. - * */ + * - returns _NON_NULL_id_count and performs table scan to verify isNull for each id in filtered rows. + */ @Query("SELECT count(*) FROM DeviceEntity d WHERE d.tenantId = :tenantId") Long countByTenantId(@Param("tenantId") UUID tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index a16245ee45..5a244cbd94 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -16,8 +16,11 @@ package org.thingsboard.server.dao.sql.device; import com.google.common.util.concurrent.ListenableFuture; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -26,6 +29,7 @@ import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceInfoFilter; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; @@ -43,9 +47,11 @@ import org.thingsboard.server.dao.model.sql.DeviceInfoEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import org.thingsboard.server.dao.util.SqlDao; +import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -58,6 +64,11 @@ import java.util.UUID; @Slf4j public class JpaDeviceDao extends JpaAbstractSearchTextDao implements DeviceDao { + @Value("${state.persistToTelemetry:false}") + private boolean persistToTelemetry; + + private Map activeColumnMap; + @Autowired private DeviceRepository deviceRepository; @@ -74,9 +85,14 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao return deviceRepository; } + @PostConstruct + public void init() { + activeColumnMap = persistToTelemetry ? DeviceInfoEntity.tsActiveColumnMap : DeviceInfoEntity.attrActiveColumnMap; + } + @Override public DeviceInfo findDeviceInfoById(TenantId tenantId, UUID deviceId) { - return DaoUtil.getData(deviceRepository.findDeviceInfoById(deviceId)); + return DaoUtil.getData(deviceRepository.findDeviceInfoById(deviceId), persistToTelemetry); } @Override @@ -104,12 +120,18 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao } @Override - public PageData findDeviceInfosByTenantId(UUID tenantId, PageLink pageLink) { + public PageData findDeviceInfosByFilter(DeviceInfoFilter filter, PageLink pageLink) { return DaoUtil.toPageData( - deviceRepository.findDeviceInfosByTenantId( - tenantId, + deviceRepository.findDeviceInfosByFilter( + filter.getTenantId().getId(), + DaoUtil.getStringId(filter.getCustomerId()), + filter.getType(), + DaoUtil.getStringId(filter.getDeviceProfileId()), + filter.getActive() != null, + persistToTelemetry, + Boolean.TRUE.equals(filter.getActive()), Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); + DaoUtil.toPageable(pageLink, activeColumnMap)), persistToTelemetry); } @Override @@ -152,16 +174,6 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao return DaoUtil.pageToPageData(deviceRepository.findIdsByDeviceProfileTransportType(transportType, DaoUtil.toPageable(pageLink))); } - @Override - public PageData findDeviceInfosByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink) { - return DaoUtil.toPageData( - deviceRepository.findDeviceInfosByTenantIdAndCustomerId( - tenantId, - customerId, - Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); - } - @Override public ListenableFuture> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List deviceIds) { return service.submit(() -> DaoUtil.convertDataList( @@ -208,26 +220,6 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao ); } - @Override - public PageData findDeviceInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink) { - return DaoUtil.toPageData( - deviceRepository.findDeviceInfosByTenantIdAndType( - tenantId, - type, - Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); - } - - @Override - public PageData findDeviceInfosByTenantIdAndDeviceProfileId(UUID tenantId, UUID deviceProfileId, PageLink pageLink) { - return DaoUtil.toPageData( - deviceRepository.findDeviceInfosByTenantIdAndDeviceProfileId( - tenantId, - deviceProfileId, - Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); - } - @Override public PageData findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { return DaoUtil.toPageData( @@ -239,28 +231,6 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao DaoUtil.toPageable(pageLink))); } - @Override - public PageData findDeviceInfosByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { - return DaoUtil.toPageData( - deviceRepository.findDeviceInfosByTenantIdAndCustomerIdAndType( - tenantId, - customerId, - type, - Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); - } - - @Override - public PageData findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(UUID tenantId, UUID customerId, UUID deviceProfileId, PageLink pageLink) { - return DaoUtil.toPageData( - deviceRepository.findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId( - tenantId, - customerId, - deviceProfileId, - Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); - } - @Override public ListenableFuture> findTenantDeviceTypesAsync(UUID tenantId) { return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(tenantId))); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index c22feb0695..b8073213cb 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -859,6 +859,16 @@ CREATE TABLE IF NOT EXISTS user_settings ( CONSTRAINT user_settings_pkey PRIMARY KEY (user_id, type) ); +CREATE OR REPLACE VIEW device_info_view as +SELECT d.* + , c.title as customer_title + , c.additional_info::json->>'isPublic' as customer_is_public + , d.type as device_profile_name + , (select bool_v from attribute_kv da where da.entity_id = d.id and da.attribute_key = 'active') as attribute_active + , (select bool_v from ts_kv_latest dt where dt.entity_id = d.id and dt.key = (select key_id from ts_kv_dictionary where key = 'active')) as ts_active +FROM device d + LEFT JOIN customer c ON c.id = d.customer_id; + DROP VIEW IF EXISTS alarm_info CASCADE; CREATE VIEW alarm_info AS SELECT a.*, diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java index 73b71ceae3..cb46e27c81 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java @@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceInfoFilter; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.OtaPackage; @@ -446,7 +447,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); device.setType("default"); - devicesTitle1.add(new DeviceInfo(deviceService.saveDevice(device), null, false, "default")); + devicesTitle1.add(new DeviceInfo(deviceService.saveDevice(device), null, false, "default", false)); } String title2 = "Device title 2"; List devicesTitle2 = new ArrayList<>(); @@ -458,14 +459,14 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); device.setType("default"); - devicesTitle2.add(new DeviceInfo(deviceService.saveDevice(device), null, false, "default")); + devicesTitle2.add(new DeviceInfo(deviceService.saveDevice(device), null, false, "default", false)); } List loadedDevicesTitle1 = new ArrayList<>(); PageLink pageLink = new PageLink(15, 0, title1); PageData pageData = null; do { - pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); + pageData = deviceService.findDeviceInfosByFilter(DeviceInfoFilter.builder().tenantId(tenantId).build(), pageLink); loadedDevicesTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -480,7 +481,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { List loadedDevicesTitle2 = new ArrayList<>(); pageLink = new PageLink(4, 0, title2); do { - pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); + pageData = deviceService.findDeviceInfosByFilter(DeviceInfoFilter.builder().tenantId(tenantId).build(), pageLink); loadedDevicesTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -497,7 +498,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { } pageLink = new PageLink(4, 0, title1); - pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); + pageData = deviceService.findDeviceInfosByFilter(DeviceInfoFilter.builder().tenantId(tenantId).build(), pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -506,7 +507,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { } pageLink = new PageLink(4, 0, title2); - pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); + pageData = deviceService.findDeviceInfosByFilter(DeviceInfoFilter.builder().tenantId(tenantId).build(), pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -605,14 +606,14 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { device.setName("Device" + i); device.setType("default"); device = deviceService.saveDevice(device); - devices.add(new DeviceInfo(deviceService.assignDeviceToCustomer(tenantId, device.getId(), customerId), customer.getTitle(), customer.isPublic(), "default")); + devices.add(new DeviceInfo(deviceService.assignDeviceToCustomer(tenantId, device.getId(), customerId), customer.getTitle(), customer.isPublic(), "default", false)); } List loadedDevices = new ArrayList<>(); PageLink pageLink = new PageLink(23); PageData pageData = null; do { - pageData = deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); + pageData = deviceService.findDeviceInfosByFilter(DeviceInfoFilter.builder().tenantId(tenantId).customerId(customerId).build(), pageLink); loadedDevices.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -627,7 +628,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { deviceService.unassignCustomerDevices(tenantId, customerId); pageLink = new PageLink(33); - pageData = deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); + pageData = deviceService.findDeviceInfosByFilter(DeviceInfoFilter.builder().tenantId(tenantId).customerId(customerId).build(), pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); } @@ -848,7 +849,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { PageLink pageLinkWithLabel = new PageLink(100, 0, "label"); List deviceInfosWithLabel = deviceService - .findDeviceInfosByTenantId(tenantId, pageLinkWithLabel).getData(); + .findDeviceInfosByFilter(DeviceInfoFilter.builder().tenantId(tenantId).build(), pageLinkWithLabel).getData(); Assert.assertFalse(deviceInfosWithLabel.isEmpty()); Assert.assertTrue( @@ -862,7 +863,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { PageLink pageLinkWithCustomer = new PageLink(100, 0, savedCustomer.getSearchText()); List deviceInfosWithCustomer = deviceService - .findDeviceInfosByTenantId(tenantId, pageLinkWithCustomer).getData(); + .findDeviceInfosByFilter(DeviceInfoFilter.builder().tenantId(tenantId).build(), pageLinkWithCustomer).getData(); Assert.assertFalse(deviceInfosWithCustomer.isEmpty()); Assert.assertTrue( @@ -877,7 +878,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { PageLink pageLinkWithType = new PageLink(100, 0, device.getType()); List deviceInfosWithType = deviceService - .findDeviceInfosByTenantId(tenantId, pageLinkWithType).getData(); + .findDeviceInfosByFilter(DeviceInfoFilter.builder().tenantId(tenantId).build(), pageLinkWithType).getData(); Assert.assertFalse(deviceInfosWithType.isEmpty()); Assert.assertTrue( @@ -907,7 +908,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { PageLink pageLinkWithLabel = new PageLink(100, 0, "label"); List deviceInfosWithLabel = deviceService - .findDeviceInfosByTenantIdAndType(tenantId, device.getType(), pageLinkWithLabel).getData(); + .findDeviceInfosByFilter(DeviceInfoFilter.builder().tenantId(tenantId).type(device.getType()).build(), pageLinkWithLabel).getData(); Assert.assertFalse(deviceInfosWithLabel.isEmpty()); Assert.assertTrue( @@ -922,7 +923,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { PageLink pageLinkWithCustomer = new PageLink(100, 0, savedCustomer.getSearchText()); List deviceInfosWithCustomer = deviceService - .findDeviceInfosByTenantIdAndType(tenantId, device.getType(), pageLinkWithCustomer).getData(); + .findDeviceInfosByFilter(DeviceInfoFilter.builder().tenantId(tenantId).type(device.getType()).build(), pageLinkWithCustomer).getData(); Assert.assertFalse(deviceInfosWithCustomer.isEmpty()); Assert.assertTrue( @@ -953,7 +954,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { PageLink pageLinkWithLabel = new PageLink(100, 0, "label"); List deviceInfosWithLabel = deviceService - .findDeviceInfosByTenantIdAndDeviceProfileId(tenantId, savedDevice.getDeviceProfileId(), pageLinkWithLabel).getData(); + .findDeviceInfosByFilter(DeviceInfoFilter.builder().tenantId(tenantId).deviceProfileId(savedDevice.getDeviceProfileId()).build(), pageLinkWithLabel).getData(); Assert.assertFalse(deviceInfosWithLabel.isEmpty()); Assert.assertTrue( @@ -968,7 +969,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { PageLink pageLinkWithCustomer = new PageLink(100, 0, savedCustomer.getSearchText()); List deviceInfosWithCustomer = deviceService - .findDeviceInfosByTenantIdAndDeviceProfileId(tenantId, savedDevice.getDeviceProfileId(), pageLinkWithCustomer).getData(); + .findDeviceInfosByFilter(DeviceInfoFilter.builder().tenantId(tenantId).deviceProfileId(savedDevice.getDeviceProfileId()).build(), pageLinkWithCustomer).getData(); Assert.assertFalse(deviceInfosWithCustomer.isEmpty()); Assert.assertTrue( From 9e589c80447badd2418585c77ee8f66291ac77dd Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 28 Apr 2023 14:10:33 +0300 Subject: [PATCH 2/5] Device info view improvements --- .../main/data/upgrade/3.4.4/schema_update.sql | 23 +++++++++++---- .../install/ThingsboardInstallService.java | 6 ++++ .../install/EntityDatabaseSchemaService.java | 3 ++ .../SqlEntityDatabaseSchemaService.java | 6 ++++ .../src/main/resources/thingsboard.yml | 4 +++ .../org/thingsboard/server/dao/DaoUtil.java | 28 ------------------- .../server/dao/model/ModelConstants.java | 3 +- .../thingsboard/server/dao/model/ToData.java | 3 -- .../dao/model/sql/DeviceInfoEntity.java | 25 +++-------------- .../dao/sql/device/DeviceRepository.java | 9 ++---- .../server/dao/sql/device/JpaDeviceDao.java | 15 ++-------- .../main/resources/sql/schema-entities.sql | 22 +++++++++++---- 12 files changed, 62 insertions(+), 85 deletions(-) diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index be3d70b163..4b452b5de0 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -16,16 +16,27 @@ -- DEVICE INFO VIEW START -DROP VIEW IF EXISTS device_info_view CASCADE; -CREATE OR REPLACE VIEW device_info_view as +DROP VIEW IF EXISTS device_info_active_attribute_view CASCADE; +CREATE OR REPLACE VIEW device_info_active_attribute_view AS SELECT d.* , c.title as customer_title - , c.additional_info::json->>'isPublic' as customer_is_public + , COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public , d.type as device_profile_name - , (select bool_v from attribute_kv da where da.entity_id = d.id and da.attribute_key = 'active') as attribute_active - , (select bool_v from ts_kv_latest dt where dt.entity_id = d.id and dt.key = (select key_id from ts_kv_dictionary where key = 'active')) as ts_active + , COALESCE(da.bool_v, FALSE) as active FROM device d - LEFT JOIN customer c ON c.id = d.customer_id; + LEFT JOIN customer c ON c.id = d.customer_id + LEFT JOIN attribute_kv da ON da.entity_id = d.id and da.attribute_key = 'active'; + +DROP VIEW IF EXISTS device_info_active_ts_view CASCADE; +CREATE OR REPLACE VIEW device_info_active_ts_view AS +SELECT d.* + , c.title as customer_title + , COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public + , d.type as device_profile_name + , COALESCE(dt.bool_v, FALSE) as active +FROM device d + LEFT JOIN customer c ON c.id = d.customer_id + LEFT JOIN ts_kv_latest dt ON dt.entity_id = d.id and dt.key = (select key_id from ts_kv_dictionary where key = 'active'); -- DEVICE INFO VIEW END diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index c3f1e44d12..ad05c77c86 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -53,6 +53,9 @@ public class ThingsboardInstallService { @Value("${install.load_demo:false}") private Boolean loadDemo; + @Value("${state.persistToTelemetry:false}") + private boolean persistToTelemetry; + @Autowired private EntityDatabaseSchemaService entityDatabaseSchemaService; @@ -247,6 +250,7 @@ public class ThingsboardInstallService { case "3.4.4": log.info("Upgrading ThingsBoard from version 3.4.4 to 3.5.0 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.4.4"); + entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry); log.info("Updating system data..."); systemDataLoaderService.updateSystemWidgets(); if (!getEnv("SKIP_DEFAULT_NOTIFICATION_CONFIGS_CREATION", false)) { @@ -272,6 +276,8 @@ public class ThingsboardInstallService { entityDatabaseSchemaService.createDatabaseSchema(); + entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry); + log.info("Installing DataBase schema for timeseries..."); if (noSqlKeyspaceService != null) { diff --git a/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java index 8d040d365e..c05dd243ba 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java @@ -16,4 +16,7 @@ package org.thingsboard.server.service.install; public interface EntityDatabaseSchemaService extends DatabaseSchemaService { + + void createOrUpdateDeviceInfoView(boolean activityStateInTelemetry); + } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java index 60513765a5..10ce5dd0ee 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java @@ -39,4 +39,10 @@ public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSer executeQueryFromFile(SCHEMA_ENTITIES_IDX_PSQL_ADDON_SQL); } + @Override + public void createOrUpdateDeviceInfoView(boolean activityStateInTelemetry) { + String sourceViewName = activityStateInTelemetry ? "device_info_active_ts_view" : "device_info_active_attribute_view"; + executeQuery("DROP VIEW IF EXISTS device_info_view CASCADE;"); + executeQuery("CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM " + sourceViewName + ";"); + } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 1bc8c19a82..7d027c3836 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -645,6 +645,10 @@ state: # Should be greater then transport.sessions.report_timeout defaultInactivityTimeoutInSec: "${DEFAULT_INACTIVITY_TIMEOUT:600}" defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:60}" + # Controls whether we store device 'active' flag in attributes (default) or telemetry. + # If you device to change this parameter, you should re-create the device info view as one of the following: + # If 'persistToTelemetry' is changed from 'false' to 'true': 'CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_ts_view;' + # If 'persistToTelemetry' is changed from 'true' to 'false': 'CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_attribute_view;' persistToTelemetry: "${PERSIST_STATE_TO_TELEMETRY:false}" tbel: diff --git a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java index 95f3eab209..60e24c6c65 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java @@ -46,12 +46,6 @@ public abstract class DaoUtil { return new PageData<>(data, page.getTotalPages(), page.getTotalElements(), page.hasNext()); } - public static PageData toPageData(Page> page, Object... params) { - List data = convertDataList(page.getContent(), params); - return new PageData<>(data, page.getTotalPages(), page.getTotalElements(), page.hasNext()); - } - - public static PageData pageToPageData(Page page) { return new PageData<>(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.hasNext()); } @@ -85,19 +79,6 @@ public abstract class DaoUtil { return list; } - public static List convertDataList(Collection> toDataList, Object... params) { - List list = Collections.emptyList(); - if (toDataList != null && !toDataList.isEmpty()) { - list = new ArrayList<>(); - for (ToData object : toDataList) { - if (object != null) { - list.add(object.toData(params)); - } - } - } - return list; - } - public static T getData(ToData data) { T object = null; if (data != null) { @@ -106,15 +87,6 @@ public abstract class DaoUtil { return object; } - public static T getData(ToData data, Object... params) { - T object = null; - if (data != null) { - object = data.toData(params); - } - return object; - } - - public static T getData(Optional> data) { T object = null; if (data.isPresent()) { 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 41d6685fd8..122716181c 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 @@ -172,8 +172,7 @@ public class ModelConstants { public static final String DEVICE_CUSTOMER_TITLE_PROPERTY = "customer_title"; public static final String DEVICE_CUSTOMER_IS_PUBLIC_PROPERTY = "customer_is_public"; public static final String DEVICE_DEVICE_PROFILE_NAME_PROPERTY = "device_profile_name"; - public static final String DEVICE_ATTR_ACTIVE_PROPERTY = "attribute_active"; - public static final String DEVICE_TS_ACTIVE_PROPERTY = "ts_active"; + public static final String DEVICE_ACTIVE_PROPERTY = "active"; public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text"; public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ToData.java b/dao/src/main/java/org/thingsboard/server/dao/model/ToData.java index 0e69506660..1e3829c33c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ToData.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ToData.java @@ -29,7 +29,4 @@ public interface ToData { */ T toData(); - default T toData(Object... params) { - return toData(); - } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java index 40dba47048..4b2d61c8a0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java @@ -24,8 +24,6 @@ import org.thingsboard.server.dao.model.ModelConstants; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; -import java.util.HashMap; -import java.util.Map; @Data @EqualsAndHashCode(callSuper = true) @@ -34,23 +32,14 @@ import java.util.Map; @Table(name = ModelConstants.DEVICE_INFO_VIEW_COLUMN_FAMILY_NAME) public class DeviceInfoEntity extends AbstractDeviceEntity { - public static final Map attrActiveColumnMap = new HashMap<>(); - public static final Map tsActiveColumnMap = new HashMap<>(); - static { - attrActiveColumnMap.put("active", "attributeActive"); - tsActiveColumnMap.put("active", "tsActive"); - } - @Column(name = ModelConstants.DEVICE_CUSTOMER_TITLE_PROPERTY) private String customerTitle; @Column(name = ModelConstants.DEVICE_CUSTOMER_IS_PUBLIC_PROPERTY) - private Boolean customerIsPublic; + private boolean customerIsPublic; @Column(name = ModelConstants.DEVICE_DEVICE_PROFILE_NAME_PROPERTY) private String deviceProfileName; - @Column(name = ModelConstants.DEVICE_ATTR_ACTIVE_PROPERTY) - private Boolean attributeActive; - @Column(name = ModelConstants.DEVICE_TS_ACTIVE_PROPERTY) - private Boolean tsActive; + @Column(name = ModelConstants.DEVICE_ACTIVE_PROPERTY) + private boolean active; public DeviceInfoEntity() { super(); @@ -59,13 +48,7 @@ public class DeviceInfoEntity extends AbstractDeviceEntity { @Override public DeviceInfo toData() { - return toData(false); + return new DeviceInfo(super.toDevice(), customerTitle, customerIsPublic, deviceProfileName, active); } - @Override - public DeviceInfo toData(Object... persistToTelemetry) { - boolean attr = persistToTelemetry.length == 0 || !(Boolean) persistToTelemetry[0]; - return new DeviceInfo(super.toDevice(), customerTitle, Boolean.TRUE.equals(customerIsPublic), deviceProfileName, - attr ? Boolean.TRUE.equals(attributeActive) : Boolean.TRUE.equals(tsActive)); - } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index ac411a580b..947998e954 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -21,10 +21,6 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.DeviceIdInfo; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.ExportableEntityRepository; import org.thingsboard.server.dao.model.sql.DeviceEntity; import org.thingsboard.server.dao.model.sql.DeviceInfoEntity; @@ -125,7 +121,7 @@ public interface DeviceRepository extends JpaRepository, Exp "AND (:customerId IS NULL OR d.customerId = uuid(:customerId)) " + "AND ((:deviceType) IS NULL OR d.type = :deviceType) " + "AND (:deviceProfileId IS NULL OR d.deviceProfileId = uuid(:deviceProfileId)) " + - "AND ((:filterByActive) IS FALSE OR (((:persistToTelemetry) IS TRUE AND d.tsActive = :deviceActive) OR ((:persistToTelemetry) IS FALSE AND d.attributeActive = :deviceActive))) " + + "AND ((:filterByActive) IS FALSE OR d.active = :deviceActive) " + "AND (LOWER(d.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + "OR LOWER(d.label) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + "OR LOWER(d.type) LIKE LOWER(CONCAT('%', :textSearch, '%')) " + @@ -135,7 +131,6 @@ public interface DeviceRepository extends JpaRepository, Exp @Param("deviceType") String type, @Param("deviceProfileId") String deviceProfileId, @Param("filterByActive") boolean filterByActive, - @Param("persistToTelemetry") boolean persistToTelemetry, @Param("deviceActive") boolean active, @Param("textSearch") String textSearch, Pageable pageable); @@ -178,7 +173,7 @@ public interface DeviceRepository extends JpaRepository, Exp /** * Count devices by tenantId. * Custom query applied because default QueryDSL produces slow count(id). - *

+ * * There is two way to count devices. * OPTIMAL: count(*) * - returns _row_count_ and use index-only scan (super fast). diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index 5a244cbd94..8b3e0af023 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -64,11 +64,6 @@ import java.util.UUID; @Slf4j public class JpaDeviceDao extends JpaAbstractSearchTextDao implements DeviceDao { - @Value("${state.persistToTelemetry:false}") - private boolean persistToTelemetry; - - private Map activeColumnMap; - @Autowired private DeviceRepository deviceRepository; @@ -85,14 +80,9 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao return deviceRepository; } - @PostConstruct - public void init() { - activeColumnMap = persistToTelemetry ? DeviceInfoEntity.tsActiveColumnMap : DeviceInfoEntity.attrActiveColumnMap; - } - @Override public DeviceInfo findDeviceInfoById(TenantId tenantId, UUID deviceId) { - return DaoUtil.getData(deviceRepository.findDeviceInfoById(deviceId), persistToTelemetry); + return DaoUtil.getData(deviceRepository.findDeviceInfoById(deviceId)); } @Override @@ -128,10 +118,9 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao filter.getType(), DaoUtil.getStringId(filter.getDeviceProfileId()), filter.getActive() != null, - persistToTelemetry, Boolean.TRUE.equals(filter.getActive()), Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink, activeColumnMap)), persistToTelemetry); + DaoUtil.toPageable(pageLink))); } @Override diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index b8073213cb..9a239f4dd2 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -859,15 +859,27 @@ CREATE TABLE IF NOT EXISTS user_settings ( CONSTRAINT user_settings_pkey PRIMARY KEY (user_id, type) ); -CREATE OR REPLACE VIEW device_info_view as +DROP VIEW IF EXISTS device_info_active_attribute_view CASCADE; +CREATE OR REPLACE VIEW device_info_active_attribute_view AS SELECT d.* , c.title as customer_title - , c.additional_info::json->>'isPublic' as customer_is_public + , COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public , d.type as device_profile_name - , (select bool_v from attribute_kv da where da.entity_id = d.id and da.attribute_key = 'active') as attribute_active - , (select bool_v from ts_kv_latest dt where dt.entity_id = d.id and dt.key = (select key_id from ts_kv_dictionary where key = 'active')) as ts_active + , COALESCE(da.bool_v, FALSE) as active FROM device d - LEFT JOIN customer c ON c.id = d.customer_id; + LEFT JOIN customer c ON c.id = d.customer_id + LEFT JOIN attribute_kv da ON da.entity_id = d.id and da.attribute_key = 'active'; + +DROP VIEW IF EXISTS device_info_active_ts_view CASCADE; +CREATE OR REPLACE VIEW device_info_active_ts_view AS +SELECT d.* + , c.title as customer_title + , COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public + , d.type as device_profile_name + , COALESCE(dt.bool_v, FALSE) as active +FROM device d + LEFT JOIN customer c ON c.id = d.customer_id + LEFT JOIN ts_kv_latest dt ON dt.entity_id = d.id and dt.key = (select key_id from ts_kv_dictionary where key = 'active'); DROP VIEW IF EXISTS alarm_info CASCADE; CREATE VIEW alarm_info AS From 7a385e34d13a6d3d24a530f451003575bfa6006b Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 28 Apr 2023 14:27:17 +0300 Subject: [PATCH 3/5] DeviceInfo for Edge support --- .../server/controller/DeviceController.java | 24 +++++-------------- .../controller/BaseDeviceControllerTest.java | 7 +++--- .../server/common/data/DeviceInfoFilter.java | 2 ++ .../dao/sql/device/DeviceRepository.java | 2 ++ .../server/dao/sql/device/JpaDeviceDao.java | 1 + .../main/resources/sql/schema-entities.sql | 3 +++ 6 files changed, 18 insertions(+), 21 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index d68aed4284..b0358b4824 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -680,7 +680,7 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/edge/{edgeId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public PageData getEdgeDevices( + public PageData getEdgeDevices( @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId, @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @@ -704,25 +704,13 @@ public class DeviceController extends BaseController { EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); checkEdgeId(edgeId, Operation.READ); TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); - PageData nonFilteredResult; + DeviceInfoFilter.DeviceInfoFilterBuilder filter = DeviceInfoFilter.builder(); + filter.tenantId(tenantId); + filter.edgeId(edgeId); if (type != null && type.trim().length() > 0) { - nonFilteredResult = deviceService.findDevicesByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink); - } else { - nonFilteredResult = deviceService.findDevicesByTenantIdAndEdgeId(tenantId, edgeId, pageLink); + filter.type(type); } - List filteredDevices = nonFilteredResult.getData().stream().filter(device -> { - try { - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, Operation.READ, device.getId(), device); - return true; - } catch (ThingsboardException e) { - return false; - } - }).collect(Collectors.toList()); - PageData filteredResult = new PageData<>(filteredDevices, - nonFilteredResult.getTotalPages(), - nonFilteredResult.getTotalElements(), - nonFilteredResult.hasNext()); - return checkNotNull(filteredResult); + return checkNotNull(deviceService.findDeviceInfosByFilter(filter.build(), pageLink)); } @ApiOperation(value = "Count devices by device profile (countByDeviceProfileAndEmptyOtaPackage)", diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java index 6ffc216dd3..87ea3e631e 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java @@ -38,6 +38,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; @@ -1287,8 +1288,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { savedDevice.getId().getId().toString(), savedEdge.getId().getId().toString(), savedEdge.getName()); testNotificationUpdateGatewayNever(); - pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?", - PAGE_DATA_DEVICE_TYPE_REF, new PageLink(100)); + PageData pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?", + new TypeReference<>() {}, new PageLink(100)); Assert.assertEquals(1, pageData.getData().size()); @@ -1303,7 +1304,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { testNotificationUpdateGatewayNever(); pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?", - PAGE_DATA_DEVICE_TYPE_REF, new PageLink(100)); + new TypeReference<>() {}, new PageLink(100)); Assert.assertEquals(0, pageData.getData().size()); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfoFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfoFilter.java index 4d5f6e0006..6f41188438 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfoFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfoFilter.java @@ -19,6 +19,7 @@ import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; @Data @@ -27,6 +28,7 @@ public class DeviceInfoFilter { private TenantId tenantId; private CustomerId customerId; + private EdgeId edgeId; private String type; private DeviceProfileId deviceProfileId; private Boolean active; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 947998e954..89ed713975 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -119,6 +119,7 @@ public interface DeviceRepository extends JpaRepository, Exp @Query("SELECT d FROM DeviceInfoEntity d " + "WHERE d.tenantId = :tenantId " + "AND (:customerId IS NULL OR d.customerId = uuid(:customerId)) " + + "AND (:edgeId IS NULL OR d.id IN (SELECT re.toId FROM RelationEntity re WHERE re.toType = 'DEVICE' AND re.relationTypeGroup = 'EDGE' AND re.relationType = 'Contains' AND re.fromType = 'EDGE' AND re.fromId = uuid(:edgeId))) " + "AND ((:deviceType) IS NULL OR d.type = :deviceType) " + "AND (:deviceProfileId IS NULL OR d.deviceProfileId = uuid(:deviceProfileId)) " + "AND ((:filterByActive) IS FALSE OR d.active = :deviceActive) " + @@ -128,6 +129,7 @@ public interface DeviceRepository extends JpaRepository, Exp "OR LOWER(d.customerTitle) LIKE LOWER(CONCAT('%', :textSearch, '%')))") Page findDeviceInfosByFilter(@Param("tenantId") UUID tenantId, @Param("customerId") String customerId, + @Param("edgeId") String edgeId, @Param("deviceType") String type, @Param("deviceProfileId") String deviceProfileId, @Param("filterByActive") boolean filterByActive, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index 8b3e0af023..1c8f23bf51 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -115,6 +115,7 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao deviceRepository.findDeviceInfosByFilter( filter.getTenantId().getId(), DaoUtil.getStringId(filter.getCustomerId()), + DaoUtil.getStringId(filter.getEdgeId()), filter.getType(), DaoUtil.getStringId(filter.getDeviceProfileId()), filter.getActive() != null, diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 9a239f4dd2..52d1d23c6b 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -881,6 +881,9 @@ FROM device d LEFT JOIN customer c ON c.id = d.customer_id LEFT JOIN ts_kv_latest dt ON dt.entity_id = d.id and dt.key = (select key_id from ts_kv_dictionary where key = 'active'); +DROP VIEW IF EXISTS device_info_view CASCADE; +CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_attribute_view; + DROP VIEW IF EXISTS alarm_info CASCADE; CREATE VIEW alarm_info AS SELECT a.*, From c11ff1434d5164a3e292cb96987a9d4bfe92889c Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 28 Apr 2023 16:30:41 +0300 Subject: [PATCH 4/5] UI: Display device activity state in table. Implement device activity state filter. --- ui-ngx/src/app/core/http/device.service.ts | 7 +- .../alarm/alarm-filter-config.component.ts | 2 +- .../device/device-info-filter.component.html | 74 +++++ .../device/device-info-filter.component.scss | 35 +++ .../device/device-info-filter.component.ts | 254 ++++++++++++++++++ .../home/components/home-components.module.ts | 3 + .../lib/alarms-table-widget.component.ts | 2 +- .../lib/home-page/home-page-widget.scss | 46 ++++ .../recent-dashboards-widget.component.html | 165 ++++++------ .../recent-dashboards-widget.component.ts | 6 +- .../usage-info-widget.component.html | 137 +++++----- .../home-page/usage-info-widget.component.ts | 68 ++--- .../device/device-table-header.component.html | 10 +- .../device/device-table-header.component.ts | 6 +- .../device/devices-table-config.resolver.ts | 121 +++++++-- .../home-links/tenant_admin_home_page.raw | 6 +- ui-ngx/src/app/shared/models/device.models.ts | 127 +++++---- ui-ngx/src/assets/home/no_data_folder_bg.svg | 22 ++ .../assets/locale/locale.constant-en_US.json | 11 + ui-ngx/src/styles.scss | 2 +- 20 files changed, 831 insertions(+), 273 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/device/device-info-filter.component.html create mode 100644 ui-ngx/src/app/modules/home/components/device/device-info-filter.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/device/device-info-filter.component.ts create mode 100644 ui-ngx/src/assets/home/no_data_folder_bg.svg diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index e5dd722419..018e202e81 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -25,7 +25,7 @@ import { ClaimResult, Device, DeviceCredentials, - DeviceInfo, + DeviceInfo, DeviceInfoQuery, DeviceSearchQuery } from '@app/shared/models/device.models'; import { EntitySubtype } from '@app/shared/models/entity-type.models'; @@ -42,6 +42,11 @@ export class DeviceService { private http: HttpClient ) { } + public getDeviceInfosByQuery(deviceInfoQuery: DeviceInfoQuery, config?: RequestConfig): Observable> { + return this.http.get>(`/api${deviceInfoQuery.toQuery()}`, + defaultHttpOptionsFromConfig(config)); + } + public getTenantDeviceInfos(pageLink: PageLink, type: string = '', config?: RequestConfig): Observable> { return this.http.get>(`/api/tenant/deviceInfos${pageLink.toQuery()}&type=${type}`, diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts index c6b8ee86e2..d018bfe503 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts @@ -190,7 +190,7 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal $event.stopPropagation(); } const config = new OverlayConfig({ - panelClass: 'tb-alarm-filter-panel', + panelClass: 'tb-filter-panel', backdropClass: 'cdk-overlay-transparent-backdrop', hasBackdrop: true, maxHeight: '80vh', diff --git a/ui-ngx/src/app/modules/home/components/device/device-info-filter.component.html b/ui-ngx/src/app/modules/home/components/device/device-info-filter.component.html new file mode 100644 index 0000000000..08448aab83 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/device-info-filter.component.html @@ -0,0 +1,74 @@ + + + + + + + + + + +

+ +
+ + +
+
+ + +
+ + + + device.device-state + + + {{ 'device.any' | translate }} + + + {{ 'device.active' | translate }} + + + {{ 'device.inactive' | translate }} + + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/device/device-info-filter.component.scss b/ui-ngx/src/app/modules/home/components/device/device-info-filter.component.scss new file mode 100644 index 0000000000..1c10e244b5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/device-info-filter.component.scss @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2023 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. + */ +:host { + display: block; + overflow: hidden; + max-width: 100%; + .mdc-button { + max-width: 100%; + } +} + +:host ::ng-deep { + .mdc-button { + .mat-icon { + min-width: 24px; + } + .mdc-button__label { + overflow: hidden; + text-overflow: ellipsis; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/device/device-info-filter.component.ts b/ui-ngx/src/app/modules/home/components/device/device-info-filter.component.ts new file mode 100644 index 0000000000..1f5b35a3ef --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/device-info-filter.component.ts @@ -0,0 +1,254 @@ +/// +/// Copyright © 2016-2023 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. +/// + +import { + ChangeDetectorRef, + Component, + ElementRef, + forwardRef, + Inject, + InjectionToken, + Input, + OnDestroy, + OnInit, + Optional, + TemplateRef, + ViewChild, + ViewContainerRef +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { coerceBoolean } from '@shared/decorators/coerce-boolean'; +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { TemplatePortal } from '@angular/cdk/portal'; +import { TranslateService } from '@ngx-translate/core'; +import { DeviceInfoFilter } from '@shared/models/device.models'; +import { isDefinedAndNotNull } from '@core/utils'; +import { EntityInfoData } from '@shared/models/entity.models'; +import { DeviceProfileService } from '@core/http/device-profile.service'; + +export const DEVICE_FILTER_CONFIG_DATA = new InjectionToken('DeviceFilterConfigData'); + +export interface DeviceFilterConfigData { + panelMode: boolean; + deviceInfoFilter: DeviceInfoFilter; +} + +// @dynamic +@Component({ + selector: 'tb-device-info-filter', + templateUrl: './device-info-filter.component.html', + styleUrls: ['./device-info-filter.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DeviceInfoFilterComponent), + multi: true + } + ] +}) +export class DeviceInfoFilterComponent implements OnInit, OnDestroy, ControlValueAccessor { + + @ViewChild('deviceFilterPanel') + deviceFilterPanel: TemplateRef; + + @Input() disabled: boolean; + + @coerceBoolean() + @Input() + buttonMode = true; + + panelMode = false; + + buttonDisplayValue = this.translate.instant('device.device-filter'); + + deviceInfoFilterForm: UntypedFormGroup; + + deviceFilterOverlayRef: OverlayRef; + + panelResult: DeviceInfoFilter = null; + + private deviceProfileInfo: EntityInfoData; + + private deviceInfoFilter: DeviceInfoFilter; + + private propagateChange = (_: any) => {}; + + constructor(@Optional() @Inject(DEVICE_FILTER_CONFIG_DATA) + private data: DeviceFilterConfigData | undefined, + @Optional() + private overlayRef: OverlayRef, + private fb: UntypedFormBuilder, + private translate: TranslateService, + private overlay: Overlay, + private nativeElement: ElementRef, + private viewContainerRef: ViewContainerRef, + private deviceProfileService: DeviceProfileService, + private cd: ChangeDetectorRef) { + } + + ngOnInit(): void { + if (this.data) { + this.panelMode = this.data.panelMode; + this.deviceInfoFilter = this.data.deviceInfoFilter; + } + this.deviceInfoFilterForm = this.fb.group({ + deviceProfileId: [null, []], + active: ['', []] + }); + this.deviceInfoFilterForm.valueChanges.subscribe( + () => { + this.updateValidators(); + if (!this.buttonMode) { + this.deviceFilterUpdated(this.deviceInfoFilterForm.value); + } + } + ); + if (this.panelMode) { + this.updateDeviceInfoFilterForm(this.deviceInfoFilter); + } + } + + ngOnDestroy(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.deviceInfoFilterForm.disable({emitEvent: false}); + } else { + this.deviceInfoFilterForm.enable({emitEvent: false}); + this.updateValidators(); + } + } + + writeValue(deviceInfoFilter?: DeviceInfoFilter): void { + this.deviceInfoFilter = deviceInfoFilter; + this.updateButtonDisplayValue(); + this.updateDeviceInfoFilterForm(deviceInfoFilter); + } + + private updateValidators() { + } + + toggleDeviceFilterPanel($event: Event) { + if ($event) { + $event.stopPropagation(); + } + const config = new OverlayConfig({ + panelClass: 'tb-filter-panel', + backdropClass: 'cdk-overlay-transparent-backdrop', + hasBackdrop: true, + maxHeight: '80vh', + height: 'min-content', + minWidth: '' + }); + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'start', + originY: 'bottom', + overlayX: 'start', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(this.nativeElement) + .withPositions([connectedPosition]); + + this.deviceFilterOverlayRef = this.overlay.create(config); + this.deviceFilterOverlayRef.backdropClick().subscribe(() => { + this.deviceFilterOverlayRef.dispose(); + }); + this.deviceFilterOverlayRef.attach(new TemplatePortal(this.deviceFilterPanel, + this.viewContainerRef)); + } + + cancel() { + this.updateDeviceInfoFilterForm(this.deviceInfoFilter); + if (this.overlayRef) { + this.overlayRef.dispose(); + } else { + this.deviceFilterOverlayRef.dispose(); + } + } + + update() { + this.deviceFilterUpdated(this.deviceInfoFilterForm.value); + if (this.panelMode) { + this.panelResult = this.deviceInfoFilter; + } + if (this.overlayRef) { + this.overlayRef.dispose(); + } else { + this.deviceFilterOverlayRef.dispose(); + } + } + + deviceProfileChanged(deviceProfileInfo: EntityInfoData) { + this.deviceProfileInfo = deviceProfileInfo; + this.updateButtonDisplayValue(); + } + + private updateDeviceInfoFilterForm(deviceInfoFilter?: DeviceInfoFilter) { + this.deviceInfoFilterForm.patchValue({ + deviceProfileId: deviceInfoFilter?.deviceProfileId, + active: isDefinedAndNotNull(deviceInfoFilter?.active) ? deviceInfoFilter?.active : '' + }, {emitEvent: false}); + this.updateValidators(); + } + + private deviceFilterUpdated(deviceInfoFilter: DeviceInfoFilter) { + this.deviceInfoFilter = deviceInfoFilter; + if ((this.deviceInfoFilter.active as any) === '') { + this.deviceInfoFilter.active = null; + } + this.updateButtonDisplayValue(); + this.propagateChange(this.deviceInfoFilter); + } + + private updateButtonDisplayValue() { + if (this.buttonMode) { + const filterTextParts: string[] = []; + if (isDefinedAndNotNull(this.deviceInfoFilter?.deviceProfileId)) { + if (!this.deviceProfileInfo) { + this.deviceProfileService.getDeviceProfileInfo(this.deviceInfoFilter?.deviceProfileId.id, + {ignoreLoading: true, ignoreErrors: true}).subscribe( + (deviceProfileInfo) => { + this.deviceProfileChanged(deviceProfileInfo); + }); + return; + } else { + filterTextParts.push(this.deviceProfileInfo.name); + } + } + if (isDefinedAndNotNull(this.deviceInfoFilter?.active)) { + const translationKey = this.deviceInfoFilter?.active ? 'device.active' : 'device.inactive'; + filterTextParts.push(this.translate.instant(translationKey)); + } + if (!filterTextParts.length) { + this.buttonDisplayValue = this.translate.instant('device.device-filter-title'); + } else { + this.buttonDisplayValue = this.translate.instant('device.filter-title') + `: ${filterTextParts.join(', ')}`; + } + this.cd.detectChanges(); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 467d006aab..ffa2d11b99 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -181,6 +181,7 @@ import { SendNotificationButtonComponent } from '@home/components/notification/s import { AlarmFilterConfigComponent } from '@home/components/alarm/alarm-filter-config.component'; import { AlarmAssigneeSelectPanelComponent } from '@home/components/alarm/alarm-assignee-select-panel.component'; import { AlarmAssigneeSelectComponent } from '@home/components/alarm/alarm-assignee-select.component'; +import { DeviceInfoFilterComponent } from '@home/components/device/device-info-filter.component'; @NgModule({ declarations: @@ -282,6 +283,7 @@ import { AlarmAssigneeSelectComponent } from '@home/components/alarm/alarm-assig DeviceProfileComponent, DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, + DeviceInfoFilterComponent, AssetProfileComponent, AssetProfileDialogComponent, AssetProfileAutocompleteComponent, @@ -425,6 +427,7 @@ import { AlarmAssigneeSelectComponent } from '@home/components/alarm/alarm-assig DeviceProfileComponent, DeviceProfileDialogComponent, AddDeviceProfileDialogComponent, + DeviceInfoFilterComponent, RuleChainAutocompleteComponent, DeviceWizardDialogComponent, AssetProfileComponent, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index abf43a8926..6b0537d968 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -591,7 +591,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, const target = $event.target || $event.srcElement || $event.currentTarget; const config = new OverlayConfig(); config.backdropClass = 'cdk-overlay-transparent-backdrop'; - config.panelClass = 'tb-alarm-filter-panel'; + config.panelClass = 'tb-filter-panel'; config.hasBackdrop = true; const connectedPosition: ConnectedPosition = { originX: 'end', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widget.scss b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widget.scss index 912ebbb63e..4cad7a6971 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widget.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widget.scss @@ -67,4 +67,50 @@ color: inherit; } } + + .tb-no-data-available { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + + .tb-no-data-bg { + margin: 10px; + position: relative; + flex: 1; + width: 100%; + max-height: 100px; + &:before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: #305680; + -webkit-mask-image: url(/assets/home/no_data_folder_bg.svg); + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; + -webkit-mask-position: center; + mask-image: url(/assets/home/no_data_folder_bg.svg); + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + } + } + + .tb-no-data-text { + font-weight: 500; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.54); + @media #{$mat-md-lg} { + font-size: 12px; + line-height: 16px; + } + } + } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.html index 40a1eefe40..ba7614934d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.html @@ -16,91 +16,102 @@ -->
- - - -
- - - - - - {{ lastVisitedDashboard.starred ? 'star' : 'star_border' }} - - - - - {{ 'widgets.recent-dashboards.name' | translate }} - - - {{ lastVisitedDashboard.title }} - - - - - {{ 'widgets.recent-dashboards.last-viewed' | translate }} - - - {{ lastVisitedDashboard.lastVisited | dateAgo:{applyAgo: true} }} - - - - -
+ +
+ {{ 'widgets.recent-dashboards.title' | translate }} + - -
-
-
{{ 'widgets.recent-dashboards.no-last-viewed-dashboards' | translate }}
+
+ + +
+ + + + + + {{ lastVisitedDashboard.starred ? 'star' : 'star_border' }} + + + + + {{ 'widgets.recent-dashboards.name' | translate }} + + + {{ lastVisitedDashboard.title }} + + + + + {{ 'widgets.recent-dashboards.last-viewed' | translate }} + + + {{ lastVisitedDashboard.lastVisited | dateAgo:{applyAgo: true} }} + + + + +
-
-
- -
-
-
- {{ dashboard.starred ? 'star' : 'star_border' }} + +
+
+
{{ 'widgets.recent-dashboards.no-last-viewed-dashboards' | translate }}
-
- {{ dashboard.title }} + + + +
+
+
+ {{ dashboard.starred ? 'star' : 'star_border' }} +
+
+ +
+ + +
+
-
- -
- + +
+
{{ 'widgets.recent-dashboards.title' | translate }}
+
+
+
+
widgets.home.no-data-available
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.ts index 33b273ec9f..4bc6ac9982 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.ts @@ -74,6 +74,7 @@ export class RecentDashboardsWidgetComponent extends PageComponent implements On lastVisitedDashboardsPageLink: PageLink; starredDashboardValue = null; + hasDashboardsAccess = true; dirty = false; @@ -84,7 +85,10 @@ export class RecentDashboardsWidgetComponent extends PageComponent implements On } ngOnInit() { - this.reload(); + this.hasDashboardsAccess = [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER].includes(this.authUser.authority); + if (this.hasDashboardsAccess) { + this.reload(); + } } reload() { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.html index 264b6970ad..a0beeacdb2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.html @@ -16,74 +16,85 @@ -->
- - - -
-
-
device.devices
-
asset.assets
-
user.users
-
dashboard.dashboards
-
customer.customers
-
-
-
-
{{ usageInfo?.devices | shortNumber }} / {{ maxValue(usageInfo?.maxDevices) }}
-
{{ usageInfo?.assets | shortNumber }} / {{ maxValue(usageInfo?.maxAssets) }}
-
{{ usageInfo?.users | shortNumber }} / {{ maxValue(usageInfo?.maxUsers) }}
-
{{ usageInfo?.dashboards | shortNumber }} / {{ maxValue(usageInfo?.maxDashboards) }}
-
{{ usageInfo?.customers | shortNumber }} / {{ maxValue(usageInfo?.maxCustomers) }}
+ + + + +
+
+
device.devices
+
asset.assets
+
user.users
+
dashboard.dashboards
+
customer.customers
-
- - - - - +
+
+
{{ usageInfo?.devices | shortNumber }} / {{ maxValue(usageInfo?.maxDevices) }}
+
{{ usageInfo?.assets | shortNumber }} / {{ maxValue(usageInfo?.maxAssets) }}
+
{{ usageInfo?.users | shortNumber }} / {{ maxValue(usageInfo?.maxUsers) }}
+
{{ usageInfo?.dashboards | shortNumber }} / {{ maxValue(usageInfo?.maxDashboards) }}
+
{{ usageInfo?.customers | shortNumber }} / {{ maxValue(usageInfo?.maxCustomers) }}
+
+
+ + + + + +
-
-
- -
-
-
api-usage.transport-messages
-
api-usage.javascript
-
api-usage.alarms-created
-
api-usage.email
-
api-usage.sms
-
-
-
-
{{ usageInfo?.transportMessages | shortNumber }} / {{ maxValue(usageInfo?.maxTransportMessages) }}
-
{{ usageInfo?.jsExecutions | shortNumber }} / {{ maxValue(usageInfo?.maxJsExecutions) }}
-
{{ usageInfo?.alarms | shortNumber }} / {{ maxValue(usageInfo?.maxAlarms) }}
-
{{ usageInfo?.emails | shortNumber }} / {{ maxValue(usageInfo?.maxEmails) }}
-
{{ usageInfo?.sms | shortNumber }} / {{ maxValue(usageInfo?.maxSms) }}
+ + +
+
+
api-usage.transport-messages
+
api-usage.javascript
+
api-usage.alarms-created
+
api-usage.email
+
api-usage.sms
-
- - - - - +
+
+
{{ usageInfo?.transportMessages | shortNumber }} / {{ maxValue(usageInfo?.maxTransportMessages) }}
+
{{ usageInfo?.jsExecutions | shortNumber }} / {{ maxValue(usageInfo?.maxJsExecutions) }}
+
{{ usageInfo?.alarms | shortNumber }} / {{ maxValue(usageInfo?.maxAlarms) }}
+
{{ usageInfo?.emails | shortNumber }} / {{ maxValue(usageInfo?.maxEmails) }}
+
{{ usageInfo?.sms | shortNumber }} / {{ maxValue(usageInfo?.maxSms) }}
+
+
+ + + + + +
-
-
+ + + +
+
{{ 'widgets.usage-info.title' | translate }}
+
+
+
+
widgets.home.no-data-available
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.ts index ed5ff7dafc..03148d501a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.ts @@ -44,6 +44,8 @@ export class UsageInfoWidgetComponent extends PageComponent implements OnInit, O entityItemCritical: {[key: string]: boolean} = {}; apiCallItemCritical: {[key: string]: boolean} = {}; + hasUsageInfoAccess = true; + constructor(protected store: Store, private cd: ChangeDetectorRef, private shortNumberPipe: ShortNumberPipe, @@ -52,41 +54,43 @@ export class UsageInfoWidgetComponent extends PageComponent implements OnInit, O } ngOnInit() { - (this.authUser.authority === Authority.TENANT_ADMIN ? - this.usageInfoService.getUsageInfo() : of(null)).subscribe( - (usageInfo) => { - this.usageInfo = usageInfo; - this.entityItemCritical.devices = this.isItemCritical(this.usageInfo?.devices, this.usageInfo?.maxDevices); - this.entityItemCritical.assets = this.isItemCritical(this.usageInfo?.assets, this.usageInfo?.maxAssets); - this.entityItemCritical.users = this.isItemCritical(this.usageInfo?.users, this.usageInfo?.maxUsers); - this.entityItemCritical.dashboards = this.isItemCritical(this.usageInfo?.dashboards, this.usageInfo?.maxDashboards); - this.entityItemCritical.customers = this.isItemCritical(this.usageInfo?.customers, this.usageInfo?.maxCustomers); - this.apiCallItemCritical.transportMessages = this.isItemCritical(this.usageInfo?.transportMessages, - this.usageInfo?.maxTransportMessages); - this.apiCallItemCritical.jsExecutions = this.isItemCritical(this.usageInfo?.jsExecutions, this.usageInfo?.maxJsExecutions); - this.apiCallItemCritical.alarms = this.isItemCritical(this.usageInfo?.alarms, this.usageInfo?.maxAlarms); - this.apiCallItemCritical.emails = this.isItemCritical(this.usageInfo?.emails, this.usageInfo?.maxEmails); - this.apiCallItemCritical.sms = this.isItemCritical(this.usageInfo?.sms, this.usageInfo?.maxSms); - let entitiesHasCriticalItem = false; - let apiCallsHasCriticalItem = false; - for (const key of Object.keys(this.entityItemCritical)) { - if (this.entityItemCritical[key]) { - entitiesHasCriticalItem = true; - break; + this.hasUsageInfoAccess = this.authUser.authority === Authority.TENANT_ADMIN; + if (this.hasUsageInfoAccess) { + this.usageInfoService.getUsageInfo().subscribe( + (usageInfo) => { + this.usageInfo = usageInfo; + this.entityItemCritical.devices = this.isItemCritical(this.usageInfo?.devices, this.usageInfo?.maxDevices); + this.entityItemCritical.assets = this.isItemCritical(this.usageInfo?.assets, this.usageInfo?.maxAssets); + this.entityItemCritical.users = this.isItemCritical(this.usageInfo?.users, this.usageInfo?.maxUsers); + this.entityItemCritical.dashboards = this.isItemCritical(this.usageInfo?.dashboards, this.usageInfo?.maxDashboards); + this.entityItemCritical.customers = this.isItemCritical(this.usageInfo?.customers, this.usageInfo?.maxCustomers); + this.apiCallItemCritical.transportMessages = this.isItemCritical(this.usageInfo?.transportMessages, + this.usageInfo?.maxTransportMessages); + this.apiCallItemCritical.jsExecutions = this.isItemCritical(this.usageInfo?.jsExecutions, this.usageInfo?.maxJsExecutions); + this.apiCallItemCritical.alarms = this.isItemCritical(this.usageInfo?.alarms, this.usageInfo?.maxAlarms); + this.apiCallItemCritical.emails = this.isItemCritical(this.usageInfo?.emails, this.usageInfo?.maxEmails); + this.apiCallItemCritical.sms = this.isItemCritical(this.usageInfo?.sms, this.usageInfo?.maxSms); + let entitiesHasCriticalItem = false; + let apiCallsHasCriticalItem = false; + for (const key of Object.keys(this.entityItemCritical)) { + if (this.entityItemCritical[key]) { + entitiesHasCriticalItem = true; + break; + } } - } - for (const key of Object.keys(this.apiCallItemCritical)) { - if (this.apiCallItemCritical[key]) { - apiCallsHasCriticalItem = true; - break; + for (const key of Object.keys(this.apiCallItemCritical)) { + if (this.apiCallItemCritical[key]) { + apiCallsHasCriticalItem = true; + break; + } } + if (apiCallsHasCriticalItem && !entitiesHasCriticalItem) { + this.toggleValue = 'apiCalls'; + } + this.cd.markForCheck(); } - if (apiCallsHasCriticalItem && !entitiesHasCriticalItem) { - this.toggleValue = 'apiCalls'; - } - this.cd.markForCheck(); - } - ); + ); + } } maxValue(max: number): number | string { diff --git a/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.html b/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.html index 043f1b1d17..c8fdbf81dd 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.html @@ -15,10 +15,6 @@ limitations under the License. --> - - + + diff --git a/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts index 6f424c8bb1..e169de5f07 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts @@ -18,7 +18,7 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityTableHeaderComponent } from '../../components/entity/entity-table-header.component'; -import { DeviceInfo } from '@app/shared/models/device.models'; +import { DeviceInfo, DeviceInfoFilter } from '@app/shared/models/device.models'; import { EntityType } from '@shared/models/entity-type.models'; import { DeviceProfileId } from '../../../../shared/models/id/device-profile-id'; @@ -35,8 +35,8 @@ export class DeviceTableHeaderComponent extends EntityTableHeaderComponent> { @@ -103,17 +118,16 @@ export class DevicesTableConfigResolver implements Resolve this.translate.instant('device.delete-devices-text'); this.config.loadEntity = id => this.deviceService.getDeviceInfo(id.id); - this.config.saveEntity = device => { - return this.deviceService.saveDevice(device).pipe( + this.config.saveEntity = device => this.deviceService.saveDevice(device).pipe( tap(() => { this.broadcast.broadcast('deviceSaved'); }), mergeMap((savedDevice) => this.deviceService.getDeviceInfo(savedDevice.id.id) )); - }; this.config.onEntityAction = action => this.onDeviceAction(action, this.config); this.config.detailsReadonly = () => (this.config.componentsData.deviceScope === 'customer_user' || this.config.componentsData.deviceScope === 'edge_customer_user'); + this.config.onLoadAction = (route) => this.onLoadAction(route); this.config.headerComponent = DeviceTableHeaderComponent; @@ -123,7 +137,7 @@ export class DevicesTableConfigResolver implements Resolve(), edgeId: routeParams.edgeId }; @@ -162,7 +176,8 @@ export class DevicesTableConfigResolver implements Resolve this.config.componentsData.deviceScope === 'tenant'; return this.config; @@ -170,48 +185,97 @@ export class DevicesTableConfigResolver implements Resolve> { const columns: Array> = [ new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '150px'), new EntityTableColumn('name', 'device.name', '25%'), new EntityTableColumn('deviceProfileName', 'device-profile.device-profile', '25%'), - new EntityTableColumn('label', 'device.label', '25%') + new EntityTableColumn('label', 'device.label', '25%'), + new EntityTableColumn('active', 'device.state', '80px', + entity => this.deviceState(entity), entity => this.deviceStateStyle(entity)) ]; if (deviceScope === 'tenant') { columns.push( new EntityTableColumn('customerTitle', 'customer.customer', '25%'), new EntityTableColumn('customerIsPublic', 'device.public', '60px', - entity => { - return checkBoxCell(entity.customerIsPublic); - }, () => ({}), false), + entity => checkBoxCell(entity.customerIsPublic), () => ({})), ); } columns.push( new EntityTableColumn('gateway', 'device.is-gateway', '60px', - entity => { - return checkBoxCell(entity.additionalInfo && entity.additionalInfo.gateway); - }, () => ({}), false) + entity => checkBoxCell(entity.additionalInfo && entity.additionalInfo.gateway), () => ({}), false) ); return columns; } + private deviceState(device: DeviceInfo): string { + let translateKey = 'device.active'; + let backgroundColor = 'rgba(25, 128, 56, 0.08)'; + if (!device.active) { + translateKey = 'device.inactive'; + backgroundColor = 'rgba(209, 39, 48, 0.08)'; + } + return `
+ ${this.translate.instant(translateKey)} +
`; + } + + private deviceStateStyle(device: DeviceInfo): object { + const styleObj = { + fontSize: '14px', + color: '#198038', + cursor: 'pointer' + }; + if (!device.active) { + styleObj.color = '#d12730'; + } + return styleObj; + } + configureEntityFunctions(deviceScope: string): void { + this.config.entitiesFetchFunction = pageLink => this.deviceService.getDeviceInfosByQuery(this.prepareDeviceInfoQuery(pageLink)); if (deviceScope === 'tenant') { - this.config.entitiesFetchFunction = pageLink => - this.deviceService.getTenantDeviceInfosByDeviceProfileId(pageLink, - this.config.componentsData.deviceProfileId !== null ? - this.config.componentsData.deviceProfileId.id : ''); this.config.deleteEntity = id => this.deviceService.deleteDevice(id.id); - } else if (deviceScope === 'edge' || deviceScope === 'edge_customer_user') { - this.config.entitiesFetchFunction = pageLink => - this.deviceService.getEdgeDevices(this.config.componentsData.edgeId, pageLink, this.config.componentsData.edgeType); } else { - this.config.entitiesFetchFunction = pageLink => - this.deviceService.getCustomerDeviceInfosByDeviceProfileId(this.customerId, pageLink, - this.config.componentsData.deviceProfileId !== null ? - this.config.componentsData.deviceProfileId.id : ''); - this.config.deleteEntity = id => this.deviceService.unassignDeviceFromCustomer(id.id); + this.config.deleteEntity = () => of(); + } + } + + prepareDeviceInfoQuery(pageLink: PageLink): DeviceInfoQuery { + const deviceInfoFilter: DeviceInfoFilter = deepClone(this.config.componentsData.deviceInfoFilter); + if (this.config.componentsData.deviceScope === 'edge' || this.config.componentsData.deviceScope === 'edge_customer_user') { + deviceInfoFilter.edgeId = new EdgeId(this.config.componentsData.edgeId); + } else if (this.config.componentsData.deviceScope !== 'tenant') { + deviceInfoFilter.customerId = new CustomerId(this.customerId); } + return new DeviceInfoQuery(pageLink, deviceInfoFilter); } configureCellActions(deviceScope: string): Array> { @@ -540,7 +604,8 @@ export class DevicesTableConfigResolver implements Resolve { if (isDefinedAndNotNull(deviceCredentials)) { diff --git a/ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw b/ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw index 6735860bd4..9205fddf71 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw +++ b/ui-ngx/src/app/modules/home/pages/home-links/tenant_admin_home_page.raw @@ -648,9 +648,9 @@ "padding": "16px", "settings": { "useMarkdownTextFunction": false, - "markdownTextPattern": "", + "markdownTextPattern": "", "applyDefaultMarkdownStyle": false, - "markdownCss": ".tb-card-content {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: row;\n}\n\n.tb-content-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n gap: 12px;\n}\n\n.tb-card-header {\n height: 36px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n}\n\n.tb-item-cards {\n flex: 1;\n display: flex;\n flex-direction: row;\n gap: 12px;\n}\n\na.tb-item-card {\n flex: 1;\n display: flex;\n flex-direction: column;\n padding: 8px 12px;\n border: 1px solid;\n border-radius: 10px;\n margin-bottom: 12px;\n}\n\na.tb-item-card.tb-inactive {\n background: rgba(209, 39, 48, 0.04);\n border-color: rgba(209, 39, 48, 0.06);\n}\n\na.tb-item-card.tb-active {\n background: rgba(48, 86, 128, 0.04);\n border-color: rgba(48, 86, 128, 0.12);\n}\n\na.tb-item-card.tb-total {\n background: rgba(0, 0, 0, 0.01);\n border-color: rgba(0, 0, 0, 0.05);\n}\n\n.tb-item-title-container {\n display: grid;\n}\n\n.tb-item-title {\n font-weight: 400;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.2px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis; \n color: rgba(0, 0, 0, 0.76);\n}\n\n.tb-item-title.tb-home-widget-link:after {\n position: absolute;\n right: 0;\n}\n\na.tb-item-card:hover .tb-item-title.tb-home-widget-link:after { \n color: rgba(0, 0, 0, 0.38);\n}\n\na.tb-item-card:hover {\n box-shadow: 0px 4px 10px rgba(23, 33, 90, 0.08);\n}\n\n.tb-count-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n}\n\n.tb-count {\n font-style: normal;\n font-weight: 500;\n font-size: 24px;\n line-height: 36px;\n white-space: nowrap;\n color: rgba(0, 0, 0, 0.87);\n}\n\n@media screen and (max-width: 959px) {\n .tb-item-cards {\n flex-direction: column;\n }\n a.tb-item-card {\n margin-bottom: 0;\n }\n}\n\n@media screen and (max-width: 1279px) {\n a.tb-item-card {\n flex-direction: row;\n align-items: center;\n }\n .tb-item-title.tb-home-widget-link:after {\n position: relative;\n }\n .tb-count-container {\n align-items: flex-end;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-item-title {\n font-size: 11px;\n line-height: 16px;\n }\n .tb-count {\n font-size: 16px;\n line-height: 24px;\n }\n a.tb-item-card {\n padding: 4px 8px;\n margin-bottom: 6px;\n }\n a.tb-item-card:hover {\n box-shadow: 0px 2px 5px rgba(23, 33, 90, 0.08);\n }\n}\n" + "markdownCss": ".tb-card-content {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: row;\n}\n\n.tb-content-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n gap: 12px;\n}\n\n.tb-card-header {\n height: 36px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n}\n\n.tb-item-cards {\n flex: 1;\n display: flex;\n flex-direction: row;\n gap: 12px;\n overflow: hidden;\n}\n\na.tb-item-card {\n flex: 1;\n display: flex;\n flex-direction: column;\n padding: 8px 12px;\n border: 1px solid;\n border-radius: 10px;\n margin-bottom: 12px;\n overflow: hidden;\n justify-content: space-evenly;\n}\n\na.tb-item-card.tb-inactive {\n background: rgba(209, 39, 48, 0.04);\n border-color: rgba(209, 39, 48, 0.06);\n}\n\na.tb-item-card.tb-active {\n background: rgba(48, 86, 128, 0.04);\n border-color: rgba(48, 86, 128, 0.12);\n}\n\na.tb-item-card.tb-total {\n background: rgba(0, 0, 0, 0.01);\n border-color: rgba(0, 0, 0, 0.05);\n}\n\n.tb-item-title-container {\n display: grid;\n}\n\n.tb-item-title {\n font-weight: 400;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.2px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis; \n color: rgba(0, 0, 0, 0.76);\n}\n\n.tb-item-title.tb-home-widget-link:after {\n position: absolute;\n right: 0;\n}\n\na.tb-item-card:hover .tb-item-title.tb-home-widget-link:after { \n color: rgba(0, 0, 0, 0.38);\n}\n\na.tb-item-card:hover {\n box-shadow: 0px 4px 10px rgba(23, 33, 90, 0.08);\n}\n\n.tb-count-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n}\n\n.tb-count {\n font-style: normal;\n font-weight: 500;\n font-size: 24px;\n line-height: 36px;\n white-space: nowrap;\n color: rgba(0, 0, 0, 0.87);\n}\n\n@media screen and (max-width: 959px) {\n .tb-item-cards {\n flex-direction: column;\n }\n a.tb-item-card {\n margin-bottom: 0;\n }\n}\n\n@media screen and (max-width: 1279px) {\n a.tb-item-card {\n flex-direction: row;\n align-items: center;\n }\n .tb-item-title.tb-home-widget-link:after {\n position: relative;\n }\n .tb-count-container {\n align-items: flex-end;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-item-title {\n font-size: 11px;\n line-height: 16px;\n }\n .tb-count {\n font-size: 16px;\n line-height: 24px;\n }\n a.tb-item-card {\n padding: 4px 8px;\n margin-bottom: 6px;\n }\n a.tb-item-card:hover {\n box-shadow: 0px 2px 5px rgba(23, 33, 90, 0.08);\n }\n}\n" }, "title": "Devices", "showTitleIcon": false, @@ -804,7 +804,7 @@ "useMarkdownTextFunction": false, "markdownTextPattern": "", "applyDefaultMarkdownStyle": false, - "markdownCss": ".tb-card-content {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: row;\n}\n\n.tb-content-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n gap: 12px;\n}\n\n.tb-card-header {\n height: 36px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n}\n\n.tb-item-cards {\n flex: 1;\n display: flex;\n flex-direction: row;\n gap: 12px;\n}\n\na.tb-item-card {\n flex: 1;\n display: flex;\n flex-direction: column;\n padding: 8px 12px;\n border: 1px solid;\n border-radius: 10px;\n margin-bottom: 12px;\n}\n\na.tb-item-card.tb-critical {\n background: rgba(209, 39, 48, 0.04);\n border-color: rgba(209, 39, 48, 0.06);\n}\n\na.tb-item-card.tb-assigned {\n background: rgba(48, 86, 128, 0.04);\n border-color: rgba(48, 86, 128, 0.12);\n}\n\na.tb-item-card.tb-total {\n background: rgba(0, 0, 0, 0.01);\n border-color: rgba(0, 0, 0, 0.05);\n}\n\n.tb-item-title-container {\n display: grid;\n}\n\n.tb-item-title {\n font-weight: 400;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.2px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis; \n color: rgba(0, 0, 0, 0.76);\n}\n\n.tb-item-title.tb-home-widget-link:after {\n position: absolute;\n right: 0;\n}\n\na.tb-item-card:hover .tb-item-title.tb-home-widget-link:after { \n color: rgba(0, 0, 0, 0.38);\n}\n\na.tb-item-card:hover {\n box-shadow: 0px 4px 10px rgba(23, 33, 90, 0.08);\n}\n\n.tb-count-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n}\n\n.tb-count {\n font-style: normal;\n font-weight: 500;\n font-size: 24px;\n line-height: 36px;\n white-space: nowrap;\n color: rgba(0, 0, 0, 0.87);\n}\n\na.tb-item-card.tb-critical .tb-count:after {\n content: \"warning\";\n display: inline-block;\n position: relative;\n font-family: 'Material Icons Round';\n color: #D12730;\n vertical-align: bottom;\n margin-left: 6px;\n}\n\n@media screen and (max-width: 959px) {\n .tb-item-cards {\n flex-direction: column;\n }\n a.tb-item-card {\n margin-bottom: 0;\n }\n}\n\n@media screen and (max-width: 1279px) {\n a.tb-item-card {\n flex-direction: row;\n align-items: center;\n }\n .tb-item-title.tb-home-widget-link:after {\n position: relative;\n }\n .tb-count-container {\n align-items: flex-end;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-item-title {\n font-size: 11px;\n line-height: 16px;\n }\n .tb-count {\n font-size: 16px;\n line-height: 24px;\n }\n a.tb-item-card {\n padding: 4px 8px;\n margin-bottom: 6px;\n }\n a.tb-item-card:hover {\n box-shadow: 0px 2px 5px rgba(23, 33, 90, 0.08);\n }\n a.tb-item-card.tb-critical .tb-count:after {\n margin-left: 2px;\n }\n}\n" + "markdownCss": ".tb-card-content {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: row;\n}\n\n.tb-content-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n gap: 12px;\n}\n\n.tb-card-header {\n height: 36px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n}\n\n.tb-item-cards {\n flex: 1;\n display: flex;\n flex-direction: row;\n gap: 12px;\n overflow: hidden;\n}\n\na.tb-item-card {\n flex: 1;\n display: flex;\n flex-direction: column;\n padding: 8px 12px;\n border: 1px solid;\n border-radius: 10px;\n margin-bottom: 12px;\n overflow: hidden;\n justify-content: space-evenly;\n}\n\na.tb-item-card.tb-critical {\n background: rgba(209, 39, 48, 0.04);\n border-color: rgba(209, 39, 48, 0.06);\n}\n\na.tb-item-card.tb-assigned {\n background: rgba(48, 86, 128, 0.04);\n border-color: rgba(48, 86, 128, 0.12);\n}\n\na.tb-item-card.tb-total {\n background: rgba(0, 0, 0, 0.01);\n border-color: rgba(0, 0, 0, 0.05);\n}\n\n.tb-item-title-container {\n display: grid;\n}\n\n.tb-item-title {\n font-weight: 400;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.2px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis; \n color: rgba(0, 0, 0, 0.76);\n}\n\n.tb-item-title.tb-home-widget-link:after {\n position: absolute;\n right: 0;\n}\n\na.tb-item-card:hover .tb-item-title.tb-home-widget-link:after { \n color: rgba(0, 0, 0, 0.38);\n}\n\na.tb-item-card:hover {\n box-shadow: 0px 4px 10px rgba(23, 33, 90, 0.08);\n}\n\n.tb-count-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n}\n\n.tb-count {\n font-style: normal;\n font-weight: 500;\n font-size: 24px;\n line-height: 36px;\n white-space: nowrap;\n color: rgba(0, 0, 0, 0.87);\n}\n\na.tb-item-card.tb-critical .tb-count:after {\n content: \"warning\";\n display: inline-block;\n position: relative;\n font-family: 'Material Icons Round';\n color: #D12730;\n vertical-align: bottom;\n margin-left: 6px;\n}\n\n@media screen and (max-width: 959px) {\n .tb-item-cards {\n flex-direction: column;\n }\n a.tb-item-card {\n margin-bottom: 0;\n }\n}\n\n@media screen and (max-width: 1279px) {\n a.tb-item-card {\n flex-direction: row;\n align-items: center;\n }\n .tb-item-title.tb-home-widget-link:after {\n position: relative;\n }\n .tb-count-container {\n align-items: flex-end;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-item-title {\n font-size: 11px;\n line-height: 16px;\n }\n .tb-count {\n font-size: 16px;\n line-height: 24px;\n }\n a.tb-item-card {\n padding: 4px 8px;\n margin-bottom: 6px;\n }\n a.tb-item-card:hover {\n box-shadow: 0px 2px 5px rgba(23, 33, 90, 0.08);\n }\n a.tb-item-card.tb-critical .tb-count:after {\n margin-left: 2px;\n }\n}\n" }, "title": "Alarms", "showTitleIcon": false, diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 1cf06e9ec6..94231a26f6 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -35,6 +35,9 @@ import { getDefaultProfileObserveAttrConfig, PowerMode } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; +import { PageLink } from '@shared/models/page/page-link'; +import { isDefinedAndNotNull, isNotEmptyStr } from '@core/utils'; +import { EdgeId } from '@shared/models/id/edge-id'; export enum DeviceProfileType { DEFAULT = 'DEFAULT', @@ -328,7 +331,7 @@ export interface DeviceProvisionConfiguration { allowCreateNewDevicesByX509Certificate?: boolean; } -export function createDeviceProfileConfiguration(type: DeviceProfileType): DeviceProfileConfiguration { +export const createDeviceProfileConfiguration = (type: DeviceProfileType): DeviceProfileConfiguration => { let configuration: DeviceProfileConfiguration = null; if (type) { switch (type) { @@ -339,9 +342,9 @@ export function createDeviceProfileConfiguration(type: DeviceProfileType): Devic } } return configuration; -} +}; -export function createDeviceConfiguration(type: DeviceProfileType): DeviceConfiguration { +export const createDeviceConfiguration = (type: DeviceProfileType): DeviceConfiguration => { let configuration: DeviceConfiguration = null; if (type) { switch (type) { @@ -352,9 +355,9 @@ export function createDeviceConfiguration(type: DeviceProfileType): DeviceConfig } } return configuration; -} +}; -export function createDeviceProfileTransportConfiguration(type: DeviceTransportType): DeviceProfileTransportConfiguration { +export const createDeviceProfileTransportConfiguration = (type: DeviceTransportType): DeviceProfileTransportConfiguration => { let transportConfiguration: DeviceProfileTransportConfiguration = null; if (type) { switch (type) { @@ -408,9 +411,9 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT } } return transportConfiguration; -} +}; -export function createDeviceTransportConfiguration(type: DeviceTransportType): DeviceTransportConfiguration { +export const createDeviceTransportConfiguration = (type: DeviceTransportType): DeviceTransportConfiguration => { let transportConfiguration: DeviceTransportConfiguration = null; if (type) { switch (type) { @@ -446,7 +449,7 @@ export function createDeviceTransportConfiguration(type: DeviceTransportType): D } } return transportConfiguration; -} +}; export enum AlarmConditionType { SIMPLE = 'SIMPLE', @@ -489,7 +492,7 @@ export const AlarmScheduleTypeTranslationMap = new Map + !(!alarmRule || !alarmRule.condition || !alarmRule.condition.condition || !alarmRule.condition.condition.length); + +export const alarmRuleValidator = (control: AbstractControl): ValidationErrors | null => { const alarmRule: AlarmRule = control.value; return alarmRuleValid(alarmRule) ? null : {alarmRule: true}; -} - -function alarmRuleValid(alarmRule: AlarmRule): boolean { - if (!alarmRule || !alarmRule.condition || !alarmRule.condition.condition || !alarmRule.condition.condition.length) { - return false; - } - return true; -} +}; export interface DeviceProfileAlarm { id: string; @@ -539,7 +538,7 @@ export interface DeviceProfileAlarm { propagateRelationTypes?: Array; } -export function deviceProfileAlarmValidator(control: AbstractControl): ValidationErrors | null { +export const deviceProfileAlarmValidator = (control: AbstractControl): ValidationErrors | null => { const deviceProfileAlarm: DeviceProfileAlarm = control.value; if (deviceProfileAlarm && deviceProfileAlarm.id && deviceProfileAlarm.alarmType && deviceProfileAlarm.createRules) { @@ -564,7 +563,7 @@ export function deviceProfileAlarmValidator(control: AbstractControl): Validatio } } return {deviceProfileAlarm: true}; -} +}; export interface DeviceProfileData { @@ -718,6 +717,47 @@ export interface DeviceInfo extends Device { customerTitle: string; customerIsPublic: boolean; deviceProfileName: string; + active: boolean; +} + +export interface DeviceInfoFilter { + customerId?: CustomerId; + edgeId?: EdgeId; + type?: string; + deviceProfileId?: DeviceProfileId; + active?: boolean; +} + +export class DeviceInfoQuery { + + pageLink: PageLink; + deviceInfoFilter: DeviceInfoFilter; + + constructor(pageLink: PageLink, deviceInfoFilter: DeviceInfoFilter) { + this.pageLink = pageLink; + this.deviceInfoFilter = deviceInfoFilter; + } + + public toQuery(): string { + let query; + if (this.deviceInfoFilter.customerId) { + query = `/customer/${this.deviceInfoFilter.customerId.id}/deviceInfos`; + } else if (this.deviceInfoFilter.edgeId) { + query = `/edge/${this.deviceInfoFilter.edgeId.id}/devices`; + } else { + query = '/tenant/deviceInfos'; + } + query += this.pageLink.toQuery(); + if (isNotEmptyStr(this.deviceInfoFilter.type)) { + query += `&type=${this.deviceInfoFilter.type}`; + } else if (this.deviceInfoFilter.deviceProfileId) { + query += `&deviceProfileId=${this.deviceInfoFilter.deviceProfileId.id}`; + } + if (isDefinedAndNotNull(this.deviceInfoFilter.active)) { + query += `&active=${this.deviceInfoFilter.active}`; + } + return query; + } } export enum DeviceCredentialsType { @@ -763,13 +803,11 @@ export interface DeviceCredentialMQTTBasic { password: string; } -export function getDeviceCredentialMQTTDefault(): DeviceCredentialMQTTBasic { - return { - clientId: '', - userName: '', - password: '' - }; -} +export const getDeviceCredentialMQTTDefault = (): DeviceCredentialMQTTBasic => ({ + clientId: '', + userName: '', + password: '' +}); export interface DeviceSearchQuery extends EntitySearchQuery { deviceTypes: Array; @@ -800,44 +838,23 @@ export const dayOfWeekTranslations = new Array( 'device-profile.schedule-day.sunday' ); -export function getDayString(day: number): string { - switch (day) { - case 0: - return 'device-profile.schedule-day.monday'; - case 1: - return this.translate.instant('device-profile.schedule-day.tuesday'); - case 2: - return this.translate.instant('device-profile.schedule-day.wednesday'); - case 3: - return this.translate.instant('device-profile.schedule-day.thursday'); - case 4: - return this.translate.instant('device-profile.schedule-day.friday'); - case 5: - return this.translate.instant('device-profile.schedule-day.saturday'); - case 6: - return this.translate.instant('device-profile.schedule-day.sunday'); - } -} - -export function timeOfDayToUTCTimestamp(date: Date | number): number { +export const timeOfDayToUTCTimestamp = (date: Date | number): number => { if (typeof date === 'number' || date === null) { return 0; } return _moment.utc([1970, 0, 1, date.getHours(), date.getMinutes(), date.getSeconds(), 0]).valueOf(); -} +}; -export function utcTimestampToTimeOfDay(time = 0): Date { - return new Date(time + new Date(time).getTimezoneOffset() * 60 * 1000); -} +export const utcTimestampToTimeOfDay = (time = 0): Date => new Date(time + new Date(time).getTimezoneOffset() * 60 * 1000); -function timeOfDayToMoment(date: Date | number): _moment.Moment { +const timeOfDayToMoment = (date: Date | number): _moment.Moment => { if (typeof date === 'number' || date === null) { return _moment([1970, 0, 1, 0, 0, 0, 0]); } return _moment([1970, 0, 1, date.getHours(), date.getMinutes(), 0, 0]); -} +}; -export function getAlarmScheduleRangeText(startsOn: Date | number, endsOn: Date | number): string { +export const getAlarmScheduleRangeText = (startsOn: Date | number, endsOn: Date | number): string => { const start = timeOfDayToMoment(startsOn); const end = timeOfDayToMoment(endsOn); if (start < end) { @@ -847,4 +864,4 @@ export function getAlarmScheduleRangeText(startsOn: Date | number, endsOn: Date } return `12:00 AM${end.format('hh:mm A')}` + ` and ${start.format('hh:mm A')}12:00 PM`; -} +}; diff --git a/ui-ngx/src/assets/home/no_data_folder_bg.svg b/ui-ngx/src/assets/home/no_data_folder_bg.svg new file mode 100644 index 0000000000..5c3d9ef141 --- /dev/null +++ b/ui-ngx/src/assets/home/no_data_folder_bg.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 3aac9e1909..bf36a7d81d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1296,6 +1296,14 @@ "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", "is-gateway": "Is gateway", "overwrite-activity-time": "Overwrite activity time for connected device", + "device-filter": "Device filter", + "device-filter-title": "Device Filter", + "filter-title": "Filter", + "device-state": "Device state", + "state": "State", + "any": "Any", + "active": "Active", + "inactive": "Inactive", "public": "Public", "device-public": "Device is public", "select-device": "Select device", @@ -5131,6 +5139,9 @@ "color": "Color", "shadow-color": "Shadow color" }, + "home": { + "no-data-available": "No data available" + }, "system-info": { "cpu": "CPU", "ram": "RAM", diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index f008d765c5..39885d1191 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -266,7 +266,7 @@ pre.tb-highlight { letter-spacing: normal; } -.tb-timewindow-panel, .tb-legend-config-panel, .tb-alarm-filter-panel { +.tb-timewindow-panel, .tb-legend-config-panel, .tb-filter-panel { overflow: hidden; background: #fff; border-radius: 4px; From 89efe12c451aacf5d542a552d5e3d03fa0188c82 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 28 Apr 2023 17:09:01 +0300 Subject: [PATCH 5/5] Fix Equals And HashCode issue for Device/Asset/EntityView Info --- .../server/controller/DeviceController.java | 7 ++++ .../server/edge/AbstractEdgeTest.java | 13 +++---- .../server/edge/BaseDeviceEdgeTest.java | 35 ++++++++++++------- .../server/common/data/DeviceInfo.java | 4 +++ .../server/common/data/EntityViewInfo.java | 2 ++ .../server/common/data/asset/AssetInfo.java | 4 +++ 6 files changed, 46 insertions(+), 19 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index b0358b4824..5ba2dd6171 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -689,6 +689,10 @@ public class DeviceController extends BaseController { @RequestParam int page, @ApiParam(value = DEVICE_TYPE_DESCRIPTION) @RequestParam(required = false) String type, + @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION) + @RequestParam(required = false) String deviceProfileId, + @ApiParam(value = DEVICE_ACTIVE_PARAM_DESCRIPTION) + @RequestParam(required = false) Boolean active, @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES) @@ -707,8 +711,11 @@ public class DeviceController extends BaseController { DeviceInfoFilter.DeviceInfoFilterBuilder filter = DeviceInfoFilter.builder(); filter.tenantId(tenantId); filter.edgeId(edgeId); + filter.active(active); if (type != null && type.trim().length() > 0) { filter.type(type); + } else if (deviceProfileId != null && deviceProfileId.length() > 0) { + filter.deviceProfileId(new DeviceProfileId(toUUID(deviceProfileId))); } return checkNotNull(deviceService.findDeviceInfosByFilter(filter.build(), pageLink)); } diff --git a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java index f4370d615e..19360da30e 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; @@ -322,9 +323,9 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { UUID deviceUUID = new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB()); Device device = doGet("/api/device/" + deviceUUID, Device.class); Assert.assertNotNull(device); - List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", - new TypeReference>() {}, new PageLink(100)).getData(); - Assert.assertTrue(edgeDevices.contains(device)); + List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", + new TypeReference>() {}, new PageLink(100)).getData(); + Assert.assertTrue(edgeDevices.stream().map(DeviceInfo::getId).anyMatch(id -> id.equals(device.getId()))); testAutoGeneratedCodeByProtobuf(deviceUpdateMsg); } @@ -487,10 +488,10 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { } protected Device findDeviceByName(String deviceName) throws Exception { - List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", - new TypeReference>() { + List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", + new TypeReference>() { }, new PageLink(100)).getData(); - Optional foundDevice = edgeDevices.stream().filter(d -> d.getName().equals(deviceName)).findAny(); + Optional foundDevice = edgeDevices.stream().filter(d -> d.getName().equals(deviceName)).findAny(); Assert.assertTrue(foundDevice.isPresent()); Device device = foundDevice.get(); Assert.assertEquals(deviceName, device.getName()); diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java index 904a6246ab..fa140fc3fb 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java @@ -25,11 +25,13 @@ import io.netty.handler.codec.mqtt.MqttQoS; import org.awaitility.Awaitility; import org.junit.Assert; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.StringUtils; @@ -46,11 +48,14 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; @@ -378,18 +383,18 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest { sendAttributesRequestAndVerify(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}", "key2", "value2"); - doDelete("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/" + DataConstants.SERVER_SCOPE, "keys","key1, inactivityTimeout"); + doDelete("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/" + DataConstants.SERVER_SCOPE, "keys", "key1, inactivityTimeout"); doDelete("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/" + DataConstants.SHARED_SCOPE, "keys", "key2"); } @Test public void testSendDeleteDeviceOnEdgeToCloud() throws Exception { - Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge(); + Device savedDevice = saveDeviceOnCloudAndVerifyDeliveryToEdge(); UplinkMsg.Builder upLinkMsgBuilder = UplinkMsg.newBuilder(); DeviceUpdateMsg.Builder deviceDeleteMsgBuilder = DeviceUpdateMsg.newBuilder(); deviceDeleteMsgBuilder.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE); - deviceDeleteMsgBuilder.setIdMSB(device.getId().getId().getMostSignificantBits()); - deviceDeleteMsgBuilder.setIdLSB(device.getId().getId().getLeastSignificantBits()); + deviceDeleteMsgBuilder.setIdMSB(savedDevice.getId().getId().getMostSignificantBits()); + deviceDeleteMsgBuilder.setIdLSB(savedDevice.getId().getId().getLeastSignificantBits()); testAutoGeneratedCodeByProtobuf(deviceDeleteMsgBuilder); upLinkMsgBuilder.addDeviceUpdateMsg(deviceDeleteMsgBuilder.build()); @@ -398,12 +403,12 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest { edgeImitator.expectResponsesAmount(1); edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build()); Assert.assertTrue(edgeImitator.waitForResponses()); - device = doGet("/api/device/" + device.getUuidId(), Device.class); - Assert.assertNotNull(device); - List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", - new TypeReference>() { + DeviceInfo deviceInfo = doGet("/api/device/info/" + savedDevice.getUuidId(), DeviceInfo.class); + Assert.assertNotNull(deviceInfo); + List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", + new TypeReference>() { }, new PageLink(100)).getData(); - Assert.assertFalse(edgeDevices.contains(device)); + Assert.assertFalse(edgeDevices.contains(deviceInfo)); } @Test @@ -455,7 +460,8 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest { Assert.assertEquals(timeseriesValue, timeseries.get(timeseriesKey).get(0).get("value")); String attributeValuesUrl = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/" + DataConstants.SERVER_SCOPE; - List> attributes = doGetAsyncTyped(attributeValuesUrl, new TypeReference<>() {}); + List> attributes = doGetAsyncTyped(attributeValuesUrl, new TypeReference<>() { + }); Assert.assertEquals(3, attributes.size()); @@ -599,7 +605,8 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest { .atMost(10, TimeUnit.SECONDS) .until(() -> { String urlTemplate = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/keys/attributes/" + scope; - List actualKeys = doGetAsyncTyped(urlTemplate, new TypeReference<>() {}); + List actualKeys = doGetAsyncTyped(urlTemplate, new TypeReference<>() { + }); return actualKeys != null && !actualKeys.isEmpty() && actualKeys.contains(expectedKey); }); @@ -656,7 +663,8 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest { private Map>> loadDeviceTimeseries(Device device, String timeseriesKey) throws Exception { return doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/values/timeseries?keys=" + timeseriesKey, - new TypeReference<>() {}); + new TypeReference<>() { + }); } @Test @@ -713,7 +721,8 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest { .atMost(10, TimeUnit.SECONDS) .until(() -> { String urlTemplate = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/keys/timeseries"; - List actualKeys = doGetAsyncTyped(urlTemplate, new TypeReference<>() {}); + List actualKeys = doGetAsyncTyped(urlTemplate, new TypeReference<>() { + }); return actualKeys != null && !actualKeys.isEmpty() && actualKeys.contains("temperature"); }); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java index c1ffbeb79e..3204cc217a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java @@ -18,12 +18,16 @@ package org.thingsboard.server.common.data; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.DeviceId; @ApiModel @Data +@EqualsAndHashCode(callSuper = true) public class DeviceInfo extends Device { + private static final long serialVersionUID = -3004579925090663691L; + @ApiModelProperty(position = 13, value = "Title of the Customer that owns the device.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String customerTitle; @ApiModelProperty(position = 14, value = "Indicates special 'Public' Customer that is auto-generated to use the devices on public dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java index f203a887cb..069ea3ae5d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java @@ -17,9 +17,11 @@ package org.thingsboard.server.common.data; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.EntityViewId; @Data +@EqualsAndHashCode(callSuper = true) public class EntityViewInfo extends EntityView { @ApiModelProperty(position = 12, value = "Title of the Customer that owns the entity view.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java index fe8a36406a..b2d1fa95a2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java @@ -18,12 +18,16 @@ package org.thingsboard.server.common.data.asset; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.AssetId; @ApiModel @Data +@EqualsAndHashCode(callSuper = true) public class AssetInfo extends Asset { + private static final long serialVersionUID = -4094528227011066194L; + @ApiModelProperty(position = 10, value = "Title of the Customer that owns the asset.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String customerTitle; @ApiModelProperty(position = 11, value = "Indicates special 'Public' Customer that is auto-generated to use the assets on public dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)