From fc513c5a5c8b59e11af95f5822f92693639087ab Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 13 Feb 2025 15:39:38 +0200 Subject: [PATCH 1/8] Add bundles to widget-type-info --- .../server/controller/BaseController.java | 5 ++ .../controller/WidgetTypeController.java | 2 +- .../server/dao/widget/WidgetTypeService.java | 2 + .../common/data/widget/WidgetTypeInfo.java | 24 +++++++ .../server/dao/model/ModelConstants.java | 1 + .../dao/model/sql/WidgetTypeInfoEntity.java | 9 +++ .../dao/sql/widget/JpaWidgetTypeDao.java | 5 ++ .../mapping/AbstractEntityInfosConverter.java | 71 +++++++++++++++++++ .../WidgetBundleEntityInfosConverter.java | 29 ++++++++ .../server/dao/widget/WidgetTypeDao.java | 2 + .../dao/widget/WidgetTypeServiceImpl.java | 7 ++ .../sql/schema-views-and-functions.sql | 11 ++- 12 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractEntityInfosConverter.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/mapping/WidgetBundleEntityInfosConverter.java diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index dbe430ba18..2b248e57b3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -125,6 +125,7 @@ import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.util.ThrowingBiFunction; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.alarm.AlarmCommentService; import org.thingsboard.server.dao.asset.AssetProfileService; @@ -759,6 +760,10 @@ public abstract class BaseController { return checkEntityId(widgetTypeId, widgetTypeService::findWidgetTypeDetailsById, operation); } + WidgetTypeInfo checkWidgetTypeInfoId(WidgetTypeId widgetTypeId, Operation operation) throws ThingsboardException { + return checkEntityId(widgetTypeId, widgetTypeService::findWidgetTypeInfoById, operation); + } + Dashboard checkDashboardId(DashboardId dashboardId, Operation operation) throws ThingsboardException { return checkEntityId(dashboardId, dashboardService::findDashboardById, operation); } diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java index e4973a71c3..2c61ecac79 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java @@ -118,7 +118,7 @@ public class WidgetTypeController extends AutoCommitController { @PathVariable("widgetTypeId") String strWidgetTypeId) throws ThingsboardException { checkParameter("widgetTypeId", strWidgetTypeId); WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId)); - return new WidgetTypeInfo(checkWidgetTypeId(widgetTypeId, Operation.READ)); + return checkWidgetTypeInfoId(widgetTypeId, Operation.READ); } @ApiOperation(value = "Create Or Update Widget Type (saveWidgetType)", diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java index 9cad249787..c592777142 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java @@ -35,6 +35,8 @@ public interface WidgetTypeService extends EntityDaoService { WidgetTypeDetails findWidgetTypeDetailsById(TenantId tenantId, WidgetTypeId widgetTypeId); + WidgetTypeInfo findWidgetTypeInfoById(TenantId tenantId, WidgetTypeId widgetTypeId); + boolean widgetTypeExistsByTenantIdAndWidgetTypeId(TenantId tenantId, WidgetTypeId widgetTypeId); WidgetTypeDetails saveWidgetType(WidgetTypeDetails widgetType); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java index 1ce8ad6ddc..2ea98f0c17 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java @@ -16,13 +16,22 @@ package org.thingsboard.server.common.data.widget; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; import lombok.Data; +import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.validation.NoXss; +import java.io.Serial; +import java.util.Collections; +import java.util.List; + @Data public class WidgetTypeInfo extends BaseWidgetType { + @Serial + private static final long serialVersionUID = 1343617007959780969L; + @Schema(description = "Base64 encoded widget thumbnail", accessMode = Schema.AccessMode.READ_ONLY) private String image; @NoXss @@ -34,6 +43,9 @@ public class WidgetTypeInfo extends BaseWidgetType { @NoXss @Schema(description = "Type of the widget (timeseries, latest, control, alarm or static)", accessMode = Schema.AccessMode.READ_ONLY) private String widgetType; + @Valid + @Schema(description = "Bundles", accessMode = Schema.AccessMode.READ_ONLY) + private List bundles; public WidgetTypeInfo() { super(); @@ -53,6 +65,16 @@ public class WidgetTypeInfo extends BaseWidgetType { this.description = widgetTypeInfo.getDescription(); this.tags = widgetTypeInfo.getTags(); this.widgetType = widgetTypeInfo.getWidgetType(); + this.bundles = Collections.emptyList(); + } + + public WidgetTypeInfo(WidgetTypeInfo widgetTypeInfo, List bundles) { + super(widgetTypeInfo); + this.image = widgetTypeInfo.getImage(); + this.description = widgetTypeInfo.getDescription(); + this.tags = widgetTypeInfo.getTags(); + this.widgetType = widgetTypeInfo.getWidgetType(); + this.bundles = bundles; } public WidgetTypeInfo(WidgetTypeDetails widgetTypeDetails) { @@ -65,5 +87,7 @@ public class WidgetTypeInfo extends BaseWidgetType { } else { this.widgetType = ""; } + this.bundles = Collections.emptyList(); } + } 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 d404c3128c..9628935b55 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 @@ -312,6 +312,7 @@ public class ModelConstants { public static final String WIDGETS_BUNDLE_SCADA_PROPERTY = "scada"; public static final String WIDGETS_BUNDLE_DESCRIPTION = "description"; public static final String WIDGETS_BUNDLE_ORDER = "widgets_bundle_order"; + public static final String WIDGET_BUNDLES_PROPERTY = "bundles"; /** * Widget_type constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java index 7a9e7f35bc..c503632b91 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java @@ -17,17 +17,21 @@ package org.thingsboard.server.dao.model.sql; import io.hypersistence.utils.hibernate.type.array.StringArrayType; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Immutable; import org.hibernate.annotations.Type; +import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.widget.BaseWidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.WidgetBundleEntityInfosConverter; import java.util.HashMap; +import java.util.List; import java.util.Map; @Data @@ -58,6 +62,10 @@ public class WidgetTypeInfoEntity extends AbstractWidgetTypeEntity bundles; + public WidgetTypeInfoEntity() { super(); } @@ -70,6 +78,7 @@ public class WidgetTypeInfoEntity extends AbstractWidgetTypeEntity findSystemWidgetTypes(WidgetTypeFilter widgetTypeFilter, PageLink pageLink) { boolean deprecatedFilterEnabled = !DeprecatedFilter.ALL.equals(widgetTypeFilter.getDeprecatedFilter()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractEntityInfosConverter.java b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractEntityInfosConverter.java new file mode 100644 index 0000000000..b829060f54 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractEntityInfosConverter.java @@ -0,0 +1,71 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.util.mapping; + +import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.AttributeConverter; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.EntityInfo; +import org.thingsboard.server.common.data.EntityType; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public abstract class AbstractEntityInfosConverter implements AttributeConverter, String> { + + protected abstract EntityType getEntityType(); + + @Override + public String convertToDatabaseColumn(List attribute) { + throw new IllegalArgumentException("Not implemented!"); + } + + @Override + public List convertToEntityAttribute(String s) { + try { + JsonNode node = JacksonUtil.fromBytes(s.getBytes(StandardCharsets.UTF_8)); + if (node.isArray()) { + List entities = new ArrayList<>(); + for (int i = 0; i < node.size(); i++) { + JsonNode row = node.get(i); + UUID id = null; + String name = null; + JsonNode idNode = row.get("id"); + JsonNode nameNode = row.get("name"); + if (idNode != null && nameNode != null) { + try { + id = UUID.fromString(idNode.asText()); + } catch (Exception ignored) {} + name = nameNode.asText(); + } + if (id != null && name != null) { + entities.add(new EntityInfo(id, EntityType.WIDGETS_BUNDLE.name(), name)); + } + } + return entities; + } else { + return Collections.emptyList(); + } + } catch (Exception ex) { + String exception = String.format("Failed to convert String to %s list: %s", getEntityType(), ex.getMessage()); + throw new RuntimeException(exception, ex); + } + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/WidgetBundleEntityInfosConverter.java b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/WidgetBundleEntityInfosConverter.java new file mode 100644 index 0000000000..143de09818 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/WidgetBundleEntityInfosConverter.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.util.mapping; + +import jakarta.persistence.Converter; +import org.thingsboard.server.common.data.EntityType; + +@Converter +public class WidgetBundleEntityInfosConverter extends AbstractEntityInfosConverter { + + @Override + protected EntityType getEntityType() { + return EntityType.WIDGETS_BUNDLE; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java index d5876bbbd9..e61976b6dd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java @@ -56,6 +56,8 @@ public interface WidgetTypeDao extends Dao, ExportableEntityD boolean existsByTenantIdAndId(TenantId tenantId, UUID widgetTypeId); + WidgetTypeInfo findWidgetTypeInfoById(TenantId tenantId, UUID widgetTypeId); + PageData findSystemWidgetTypes(WidgetTypeFilter widgetTypeFilter, PageLink pageLink); PageData findAllTenantWidgetTypesByTenantId(WidgetTypeFilter widgetTypeFilter, PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java index a1e241073f..1b0ecb33a4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java @@ -87,6 +87,13 @@ public class WidgetTypeServiceImpl implements WidgetTypeService { return widgetTypeDao.findById(tenantId, widgetTypeId.getId()); } + @Override + public WidgetTypeInfo findWidgetTypeInfoById(TenantId tenantId, WidgetTypeId widgetTypeId) { + log.trace("Executing findWidgetTypeInfoById [{}]", widgetTypeId); + Validator.validateId(widgetTypeId, id -> "Incorrect widgetTypeId " + id); + return widgetTypeDao.findWidgetTypeInfoById(tenantId, widgetTypeId.getId()); + } + @Override public boolean widgetTypeExistsByTenantIdAndWidgetTypeId(TenantId tenantId, WidgetTypeId widgetTypeId) { log.trace("Executing widgetTypeExistsByTenantIdAndWidgetTypeId, tenantId [{}], widgetTypeId [{}]", tenantId, widgetTypeId); diff --git a/dao/src/main/resources/sql/schema-views-and-functions.sql b/dao/src/main/resources/sql/schema-views-and-functions.sql index 31219ced18..b394742bcd 100644 --- a/dao/src/main/resources/sql/schema-views-and-functions.sql +++ b/dao/src/main/resources/sql/schema-views-and-functions.sql @@ -288,8 +288,15 @@ $$; DROP VIEW IF EXISTS widget_type_info_view CASCADE; CREATE OR REPLACE VIEW widget_type_info_view AS -SELECT t.* - , COALESCE((t.descriptor::json->>'type')::text, '') as widget_type +SELECT t.*, + COALESCE((t.descriptor::json->>'type')::text, '') as widget_type, + array_to_json(ARRAY( + SELECT json_build_object('id', wb.widgets_bundle_id, 'name', b.title) + FROM widgets_bundle_widget wb + JOIN widgets_bundle b ON wb.widgets_bundle_id = b.id + WHERE wb.widget_type_id = t.id + ORDER BY b.title + )) AS bundles FROM widget_type t; CREATE OR REPLACE PROCEDURE cleanup_timeseries_by_ttl(IN null_uuid uuid, From 6d840e3686039d3b7df2f3cd0a34891f39645507 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 14 Feb 2025 10:56:59 +0200 Subject: [PATCH 2/8] Improve AbstractEntityInfosConverter --- .../server/dao/util/mapping/AbstractEntityInfosConverter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractEntityInfosConverter.java b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractEntityInfosConverter.java index b829060f54..98b94d17e2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractEntityInfosConverter.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractEntityInfosConverter.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.util.mapping; import com.fasterxml.jackson.databind.JsonNode; import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.EntityType; @@ -27,6 +28,7 @@ import java.util.Collections; import java.util.List; import java.util.UUID; +@Converter public abstract class AbstractEntityInfosConverter implements AttributeConverter, String> { protected abstract EntityType getEntityType(); @@ -55,7 +57,7 @@ public abstract class AbstractEntityInfosConverter implements AttributeConverter name = nameNode.asText(); } if (id != null && name != null) { - entities.add(new EntityInfo(id, EntityType.WIDGETS_BUNDLE.name(), name)); + entities.add(new EntityInfo(id, getEntityType().name(), name)); } } return entities; From 4ed379db23f5ae724334fe45a2d7b57853e16e18 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 14 Feb 2025 12:11:37 +0200 Subject: [PATCH 3/8] Fix JpaWidgetTypeDaoTest --- .../common/data/widget/WidgetTypeInfo.java | 13 ++++++++ .../dao/sql/widget/JpaWidgetTypeDaoTest.java | 33 ++++++++++--------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java index 2ea98f0c17..803dcbb945 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java @@ -90,4 +90,17 @@ public class WidgetTypeInfo extends BaseWidgetType { this.bundles = Collections.emptyList(); } + public WidgetTypeInfo(WidgetTypeDetails widgetTypeDetails, List bundles) { + super(widgetTypeDetails); + this.image = widgetTypeDetails.getImage(); + this.description = widgetTypeDetails.getDescription(); + this.tags = widgetTypeDetails.getTags(); + if (widgetTypeDetails.getDescriptor() != null && widgetTypeDetails.getDescriptor().has("type")) { + this.widgetType = widgetTypeDetails.getDescriptor().get("type").asText(); + } else { + this.widgetType = ""; + } + this.bundles = bundles; + } + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java index 8010eab4cd..ae73b65581 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java @@ -21,6 +21,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetsBundleId; @@ -51,9 +52,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -/** - * Created by Valerii Sosliuk on 4/30/2017. - */ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { // given search text should find a widget with tags, when searching by tags @@ -78,7 +76,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { final String BUNDLE_ALIAS = "BUNDLE_ALIAS"; final int WIDGET_TYPE_COUNT = 3; - List widgetTypeList; + List widgetTypeList; WidgetsBundle widgetsBundle; @Autowired @@ -107,7 +105,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { widgetTypeList.sort(Comparator.comparing(BaseWidgetType::getName)); } - WidgetTypeDetails createAndSaveWidgetType(TenantId tenantId, int number) { + WidgetTypeInfo createAndSaveWidgetType(TenantId tenantId, int number) { WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setName("WIDGET_TYPE_" + number); @@ -119,7 +117,10 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { widgetType.setDescriptor(descriptor); String[] tags = new String[]{"Tag1_"+number, "Tag2_"+number, "TEST_"+number}; widgetType.setTags(tags); - return widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType); + WidgetTypeDetails saved = widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType); + List bundles = new ArrayList<>(); + bundles.add(new EntityInfo(widgetsBundle.getId(), widgetsBundle.getName())); + return new WidgetTypeInfo(saved, bundles); } WidgetTypeDetails createAndSaveWidgetType(TenantId tenantId, int number, String[] tags) { @@ -139,7 +140,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { @After public void tearDown() { widgetsBundleDao.removeById(TenantId.SYS_TENANT_ID, widgetsBundle.getUuidId()); - for (WidgetType widgetType : widgetTypeList) { + for (WidgetTypeInfo widgetType : widgetTypeList) { widgetTypeDao.removeById(TenantId.SYS_TENANT_ID, widgetType.getUuidId()); } } @@ -160,7 +161,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { .widgetTypes(Collections.singletonList("static")).build(), new PageLink(1024, 0, "TYPE_DESCRIPTION", new SortOrder("createdTime"))); assertEquals(1, widgetTypes.getData().size()); - assertEquals(new WidgetTypeInfo(widgetTypeList.get(1)), widgetTypes.getData().get(0)); + assertEquals(widgetTypeList.get(1), widgetTypes.getData().get(0)); widgetTypes = widgetTypeDao.findSystemWidgetTypes( WidgetTypeFilter.builder() @@ -170,7 +171,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { .widgetTypes(Collections.emptyList()).build(), new PageLink(1024, 0, "hfgfd tag2_2 ghg", new SortOrder("createdTime"))); assertEquals(1, widgetTypes.getData().size()); - assertEquals(new WidgetTypeInfo(widgetTypeList.get(2)), widgetTypes.getData().get(0)); + assertEquals(widgetTypeList.get(2), widgetTypes.getData().get(0)); } @Test @@ -181,7 +182,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { Thread.sleep(2); var widgetType = saveWidgetType(TenantId.SYS_TENANT_ID, "widgetName"); sameNameList.add(widgetType); - widgetTypeList.add(widgetType); + widgetTypeList.add(new WidgetTypeInfo(widgetType)); } sameNameList.sort(Comparator.comparing(BaseWidgetType::getName).thenComparing((BaseWidgetType baseWidgetType) -> baseWidgetType.getId().getId())); List expected = sameNameList.stream().map(WidgetTypeInfo::new).collect(Collectors.toList()); @@ -254,12 +255,12 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { public void testFindTenantWidgetTypesByTenantId() { UUID tenantId = Uuids.timeBased(); for (int i = 0; i < WIDGET_TYPE_COUNT; i++) { - var widgetType = createAndSaveWidgetType(new TenantId(tenantId), i); + var widgetType = createAndSaveWidgetType(TenantId.fromUUID(tenantId), i); widgetTypeList.add(widgetType); } PageData widgetTypes = widgetTypeDao.findTenantWidgetTypesByTenantId( WidgetTypeFilter.builder() - .tenantId(new TenantId(tenantId)) + .tenantId(TenantId.fromUUID(tenantId)) .fullSearch(true) .deprecatedFilter(DeprecatedFilter.ALL) .widgetTypes(null).build(), @@ -363,13 +364,13 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { PageData widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID.getId(), widgetsBundle.getUuidId(),true, DeprecatedFilter.ALL, Collections.singletonList("latest"), new PageLink(1024, 0, "TYPE_DESCRIPTION", new SortOrder("createdTime"))); assertEquals(2, widgetTypes.getData().size()); - assertEquals(new WidgetTypeInfo(widgetTypeList.get(0)), widgetTypes.getData().get(0)); - assertEquals(new WidgetTypeInfo(widgetTypeList.get(2)), widgetTypes.getData().get(1)); + assertEquals(widgetTypeList.get(0), widgetTypes.getData().get(0)); + assertEquals(widgetTypeList.get(2), widgetTypes.getData().get(1)); widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID.getId(), widgetsBundle.getUuidId(), true, DeprecatedFilter.ALL, Collections.emptyList(), new PageLink(1024, 0, "hfgfd TEST_0 ghg", new SortOrder("createdTime"))); assertEquals(1, widgetTypes.getData().size()); - assertEquals(new WidgetTypeInfo(widgetTypeList.get(0)), widgetTypes.getData().get(0)); + assertEquals(widgetTypeList.get(0), widgetTypes.getData().get(0)); } @Test @@ -410,7 +411,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { @Test public void testFindByTenantIdAndFqn() { - WidgetType result = widgetTypeList.get(0); + WidgetTypeInfo result = widgetTypeList.get(0); assertNotNull(result); WidgetType widgetType = widgetTypeDao.findByTenantIdAndFqn(TenantId.SYS_TENANT_ID.getId(), "FQN_0"); assertEquals(result.getId(), widgetType.getId()); From 0e3edb271f7ed702fcb22ab3c499d03de4d8094a Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 14 Feb 2025 13:03:06 +0200 Subject: [PATCH 4/8] Add tests for widget bundle --- .../dao/sql/widget/JpaWidgetTypeDaoTest.java | 94 ++++++++++++++++--- 1 file changed, 82 insertions(+), 12 deletions(-) diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java index ae73b65581..693bf86d91 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java @@ -50,7 +50,9 @@ import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { @@ -115,7 +117,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { var descriptor = JacksonUtil.newObjectNode(); descriptor.put("type", number % 2 == 0 ? "latest" : "static"); widgetType.setDescriptor(descriptor); - String[] tags = new String[]{"Tag1_"+number, "Tag2_"+number, "TEST_"+number}; + String[] tags = new String[]{"Tag1_" + number, "Tag2_" + number, "TEST_" + number}; widgetType.setTags(tags); WidgetTypeDetails saved = widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType); List bundles = new ArrayList<>(); @@ -189,10 +191,10 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { PageData widgetTypesFirstPage = widgetTypeDao.findSystemWidgetTypes( WidgetTypeFilter.builder() - .tenantId(TenantId.SYS_TENANT_ID) - .fullSearch(true) - .deprecatedFilter(DeprecatedFilter.ALL) - .widgetTypes(Collections.singletonList("static")).build(), + .tenantId(TenantId.SYS_TENANT_ID) + .fullSearch(true) + .deprecatedFilter(DeprecatedFilter.ALL) + .widgetTypes(Collections.singletonList("static")).build(), new PageLink(10, 0, null, new SortOrder("name"))); assertEquals(10, widgetTypesFirstPage.getData().size()); assertThat(widgetTypesFirstPage.getData()).containsExactlyElementsOf(expected.subList(0, 10)); @@ -217,10 +219,10 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { PageData widgetTypes = widgetTypeDao.findSystemWidgetTypes( WidgetTypeFilter.builder() - .tenantId(TenantId.SYS_TENANT_ID) - .fullSearch(true) - .deprecatedFilter(DeprecatedFilter.ALL) - .widgetTypes(null).build(), + .tenantId(TenantId.SYS_TENANT_ID) + .fullSearch(true) + .deprecatedFilter(DeprecatedFilter.ALL) + .widgetTypes(null).build(), new PageLink(10, 0, searchText) ); @@ -361,7 +363,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { @Test public void testFindByWidgetTypeInfosByBundleId() { - PageData widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID.getId(), widgetsBundle.getUuidId(),true, DeprecatedFilter.ALL, Collections.singletonList("latest"), + PageData widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID.getId(), widgetsBundle.getUuidId(), true, DeprecatedFilter.ALL, Collections.singletonList("latest"), new PageLink(1024, 0, "TYPE_DESCRIPTION", new SortOrder("createdTime"))); assertEquals(2, widgetTypes.getData().size()); assertEquals(widgetTypeList.get(0), widgetTypes.getData().get(0)); @@ -427,7 +429,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { @Test public void testFindByImageLink() { - TenantId tenantId = new TenantId(UUID.randomUUID()); + TenantId tenantId = TenantId.fromUUID(UUID.randomUUID()); WidgetTypeDetails details = createAndSaveWidgetType(tenantId, 0, new String[]{"a"}); details.setDescriptor(JacksonUtil.newObjectNode().put("bg", "/image/tenant/widget.png")); widgetTypeDao.save(tenantId, details); @@ -438,14 +440,82 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { widgetTypeDao.removeById(tenantId, details.getUuidId()); } + @Test + public void testFindWidgetTypesWithBundles() { + PageData widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId( + TenantId.SYS_TENANT_ID.getId(), + widgetsBundle.getUuidId(), + true, + DeprecatedFilter.ALL, + Collections.singletonList("latest"), + new PageLink(1024, 0, "TYPE_DESCRIPTION", new SortOrder("createdTime")) + ); + + assertEquals(2, widgetTypes.getData().size()); + for (var widgetType : widgetTypes.getData()) { + assertFalse("Bundles should not be empty", widgetType.getBundles().isEmpty()); + assertEquals(BUNDLE_ALIAS, widgetType.getBundles().get(0).getName()); + } + } + + @Test + public void testAddWidgetTypeToNewBundleAndVerifyBundles() { + String newBundleTitle = "New Bundle Title"; + WidgetsBundle newWidgetsBundle = new WidgetsBundle(); + newWidgetsBundle.setAlias("NewBundle"); + newWidgetsBundle.setTitle(newBundleTitle); + newWidgetsBundle.setId(new WidgetsBundleId(UUID.randomUUID())); + newWidgetsBundle.setTenantId(TenantId.SYS_TENANT_ID); + newWidgetsBundle = widgetsBundleDao.save(TenantId.SYS_TENANT_ID, newWidgetsBundle); + + for (int i = 0; i < widgetTypeList.size(); i++) { + WidgetTypeInfo widgetType = widgetTypeList.get(i); + widgetTypeDao.saveWidgetsBundleWidget(new WidgetsBundleWidget(newWidgetsBundle.getId(), widgetType.getId(), i)); + } + + PageData widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId( + TenantId.SYS_TENANT_ID.getId(), + newWidgetsBundle.getUuidId(), + true, + DeprecatedFilter.ALL, + Collections.singletonList("latest"), + new PageLink(1024, 0, "TYPE_DESCRIPTION", new SortOrder("createdTime")) + ); + + assertEquals(2, widgetTypes.getData().size()); + WidgetTypeInfo widgetTypeInfo1 = widgetTypes.getData().get(0); + WidgetTypeInfo widgetTypeInfo2 = widgetTypes.getData().get(1); + + assertEquals(2, widgetTypeInfo1.getBundles().size()); + assertTrue("Bundles should contain 'BUNDLE_ALIAS'", widgetTypeInfo1.getBundles().stream().anyMatch(bundle -> BUNDLE_ALIAS.equals(bundle.getName()))); + assertTrue("Bundles should contain 'New Bundle Title'", widgetTypeInfo1.getBundles().stream().anyMatch(bundle -> newBundleTitle.equals(bundle.getName()))); + + assertEquals(2, widgetTypeInfo2.getBundles().size()); + assertTrue("Bundles should contain 'BUNDLE_ALIAS'", widgetTypeInfo2.getBundles().stream().anyMatch(bundle -> "BUNDLE_ALIAS".equals(bundle.getName()))); + assertTrue("Bundles should contain 'New Bundle Title'", widgetTypeInfo2.getBundles().stream().anyMatch(bundle -> newBundleTitle.equals(bundle.getName()))); + + // cleanup and verify + widgetsBundleDao.removeById(newWidgetsBundle.getTenantId(), newWidgetsBundle.getUuidId()); + widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId( + TenantId.SYS_TENANT_ID.getId(), + newWidgetsBundle.getUuidId(), + true, + DeprecatedFilter.ALL, + Collections.singletonList("latest"), + new PageLink(1024, 0, "TYPE_DESCRIPTION", new SortOrder("createdTime")) + ); + widgetTypes.getData().forEach(widgetTypeInfo -> assertEquals(1, widgetTypeInfo.getBundles().size())); + } + private WidgetTypeDetails saveWidgetType(TenantId tenantId, String name) { WidgetTypeDetails widgetType = new WidgetTypeDetails(); widgetType.setTenantId(tenantId); widgetType.setDescription("WIDGET_TYPE_DESCRIPTION" + StringUtils.randomAlphabetic(7)); widgetType.setName(name); var descriptor = JacksonUtil.newObjectNode(); - descriptor.put("type","static"); + descriptor.put("type", "static"); widgetType.setDescriptor(descriptor); return widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType); } + } From 3e4e027f3921f18b0726bf26f0be9e7075ffe004 Mon Sep 17 00:00:00 2001 From: Ekaterina Chantsova Date: Wed, 19 Feb 2025 19:00:27 +0200 Subject: [PATCH 5/8] Widget types list: widget bundles info added --- .../home/components/entity/entities-table.component.html | 3 ++- .../home/components/entity/entity-chips.component.ts | 9 +++++++-- .../home/models/entity/entities-table-config.models.ts | 3 ++- .../pages/widget/widget-types-table-config.resolver.ts | 5 ++++- ui-ngx/src/app/shared/models/widget.models.ts | 3 ++- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html index a6b204b279..1754b94e62 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html @@ -192,7 +192,8 @@ (click)="$event.stopPropagation();"> - + diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.ts index 6aad28c638..ebece2186b 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.ts @@ -18,7 +18,7 @@ import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { BaseData } from '@shared/models/base-data'; import { EntityId } from '@shared/models/id/entity-id'; import { baseDetailsPageByEntityType, EntityType } from '@app/shared/public-api'; -import { isEqual, isObject } from '@core/utils'; +import { isEqual, isNotEmptyStr, isObject } from '@core/utils'; @Component({ selector: 'tb-entity-chips', @@ -33,6 +33,9 @@ export class EntityChipsComponent implements OnChanges { @Input() key: string; + @Input() + detailsPagePrefixUrl: string; + entityDetailsPrefixUrl: string; subEntities: Array> = []; @@ -52,7 +55,9 @@ export class EntityChipsComponent implements OnChanges { if (isObject(entitiesList) && !Array.isArray(entitiesList)) { entitiesList = [entitiesList]; } - if (Array.isArray(entitiesList)) { + if (isNotEmptyStr(this.detailsPagePrefixUrl)) { + this.entityDetailsPrefixUrl = this.detailsPagePrefixUrl; + } else if (Array.isArray(entitiesList)) { if (entitiesList.length) { this.entityDetailsPrefixUrl = baseDetailsPageByEntityType.get(entitiesList[0].id.entityType as EntityType); } diff --git a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts index dbdcdcd61e..7e1a25d61a 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts @@ -144,7 +144,8 @@ export class DateEntityTableColumn> extends EntityTabl export class EntityChipsEntityTableColumn> extends BaseEntityTableColumn { constructor(public key: string, public title: string, - public width: string = '0px') { + public width: string = '0px', + public entityURL?: (entity) => string) { super('entityChips', key, title, width, false); } } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-types-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-types-table-config.resolver.ts index eac0817875..60b6f70ac7 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-types-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-types-table-config.resolver.ts @@ -20,6 +20,7 @@ import { Router } from '@angular/router'; import { checkBoxCell, DateEntityTableColumn, + EntityChipsEntityTableColumn, EntityTableColumn, EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @@ -75,7 +76,9 @@ export class WidgetTypesTableConfigResolver { this.config.columns.push( new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '150px'), - new EntityTableColumn('name', 'widget.title', '100%'), + new EntityTableColumn('name', 'widget.title', '60%'), + new EntityChipsEntityTableColumn( 'bundles', 'entity.type-widgets-bundles', '40%', + () => '/resources/widgets-library/widgets-bundles'), new EntityTableColumn('widgetType', 'widget.type', '150px', entity => entity?.widgetType ? this.translate.instant(widgetTypesData.get(entity.widgetType).name) : '', undefined, false), new EntityTableColumn('tenantId', 'widget.system', '60px', diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 5950dcf911..6e45e8705a 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -41,7 +41,7 @@ import { isNotEmptyStr, mergeDeepIgnoreArray } from '@core/utils'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; -import { HasTenantId, HasVersion } from '@shared/models/entity.models'; +import { EntityInfoData, HasTenantId, HasVersion } from '@shared/models/entity.models'; import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models'; import { TbFunction } from '@shared/models/js-function.models'; @@ -270,6 +270,7 @@ export interface WidgetTypeInfo extends BaseWidgetType { description: string; tags: string[]; widgetType: widgetType; + bundles?: EntityInfoData[]; } export interface WidgetTypeDetails extends WidgetType, ExportableEntity { From e6174a93862b928b7429f1ade41d802e8efaebe1 Mon Sep 17 00:00:00 2001 From: Ekaterina Chantsova Date: Fri, 21 Feb 2025 12:21:26 +0200 Subject: [PATCH 6/8] Fixed empty url function parameter --- .../home/components/entity/entities-table.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html index 1754b94e62..c56aae3476 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html @@ -193,7 +193,7 @@ + [detailsPagePrefixUrl]="column.entityURL ? column.entityURL(entity) : ''"> From 9a93a279435d2b3b50cacdbe682f238221e321b1 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 24 Feb 2025 14:06:04 +0200 Subject: [PATCH 7/8] Refactoring after review --- .../common/data/widget/WidgetBundleInfo.java | 39 ++++++++++ .../common/data/widget/WidgetTypeInfo.java | 25 ++----- .../dao/model/sql/WidgetTypeInfoEntity.java | 13 ++-- .../mapping/AbstractEntityInfosConverter.java | 73 ------------------- .../WidgetBundleEntityInfosConverter.java | 29 -------- 5 files changed, 51 insertions(+), 128 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetBundleInfo.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractEntityInfosConverter.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/util/mapping/WidgetBundleEntityInfosConverter.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetBundleInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetBundleInfo.java new file mode 100644 index 0000000000..f7e43fdad0 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetBundleInfo.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2024 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.widget; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.thingsboard.server.common.data.EntityInfo; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.EntityIdFactory; + +import java.io.Serial; +import java.util.UUID; + +@Value +@EqualsAndHashCode(callSuper = true) +public class WidgetBundleInfo extends EntityInfo { + + @Serial + private static final long serialVersionUID = 2132305394634509820L; + + public WidgetBundleInfo(@JsonProperty("id") UUID uuid, @JsonProperty("name") String name) { + super(EntityIdFactory.getByTypeAndUuid(EntityType.WIDGETS_BUNDLE, uuid), name); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java index 803dcbb945..f7260052a9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java @@ -18,7 +18,6 @@ package org.thingsboard.server.common.data.widget; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import lombok.Data; -import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.validation.NoXss; @@ -45,7 +44,7 @@ public class WidgetTypeInfo extends BaseWidgetType { private String widgetType; @Valid @Schema(description = "Bundles", accessMode = Schema.AccessMode.READ_ONLY) - private List bundles; + private List bundles; public WidgetTypeInfo() { super(); @@ -60,15 +59,10 @@ public class WidgetTypeInfo extends BaseWidgetType { } public WidgetTypeInfo(WidgetTypeInfo widgetTypeInfo) { - super(widgetTypeInfo); - this.image = widgetTypeInfo.getImage(); - this.description = widgetTypeInfo.getDescription(); - this.tags = widgetTypeInfo.getTags(); - this.widgetType = widgetTypeInfo.getWidgetType(); - this.bundles = Collections.emptyList(); + this(widgetTypeInfo, Collections.emptyList()); } - public WidgetTypeInfo(WidgetTypeInfo widgetTypeInfo, List bundles) { + public WidgetTypeInfo(WidgetTypeInfo widgetTypeInfo, List bundles) { super(widgetTypeInfo); this.image = widgetTypeInfo.getImage(); this.description = widgetTypeInfo.getDescription(); @@ -78,19 +72,10 @@ public class WidgetTypeInfo extends BaseWidgetType { } public WidgetTypeInfo(WidgetTypeDetails widgetTypeDetails) { - super(widgetTypeDetails); - this.image = widgetTypeDetails.getImage(); - this.description = widgetTypeDetails.getDescription(); - this.tags = widgetTypeDetails.getTags(); - if (widgetTypeDetails.getDescriptor() != null && widgetTypeDetails.getDescriptor().has("type")) { - this.widgetType = widgetTypeDetails.getDescriptor().get("type").asText(); - } else { - this.widgetType = ""; - } - this.bundles = Collections.emptyList(); + this(widgetTypeDetails, Collections.emptyList()); } - public WidgetTypeInfo(WidgetTypeDetails widgetTypeDetails, List bundles) { + public WidgetTypeInfo(WidgetTypeDetails widgetTypeDetails, List bundles) { super(widgetTypeDetails); this.image = widgetTypeDetails.getImage(); this.description = widgetTypeDetails.getDescription(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java index c503632b91..d718e5613d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetTypeInfoEntity.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.dao.model.sql; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import io.hypersistence.utils.hibernate.type.array.StringArrayType; import jakarta.persistence.Column; import jakarta.persistence.Convert; @@ -24,14 +26,13 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Immutable; import org.hibernate.annotations.Type; -import org.thingsboard.server.common.data.EntityInfo; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.widget.BaseWidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.mapping.WidgetBundleEntityInfosConverter; +import org.thingsboard.server.dao.util.mapping.JsonConverter; import java.util.HashMap; -import java.util.List; import java.util.Map; @Data @@ -62,9 +63,9 @@ public class WidgetTypeInfoEntity extends AbstractWidgetTypeEntity bundles; + private JsonNode bundles; public WidgetTypeInfoEntity() { super(); @@ -78,7 +79,7 @@ public class WidgetTypeInfoEntity extends AbstractWidgetTypeEntity() {})); return widgetTypeInfo; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractEntityInfosConverter.java b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractEntityInfosConverter.java deleted file mode 100644 index 98b94d17e2..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/AbstractEntityInfosConverter.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.util.mapping; - -import com.fasterxml.jackson.databind.JsonNode; -import jakarta.persistence.AttributeConverter; -import jakarta.persistence.Converter; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.EntityInfo; -import org.thingsboard.server.common.data.EntityType; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -@Converter -public abstract class AbstractEntityInfosConverter implements AttributeConverter, String> { - - protected abstract EntityType getEntityType(); - - @Override - public String convertToDatabaseColumn(List attribute) { - throw new IllegalArgumentException("Not implemented!"); - } - - @Override - public List convertToEntityAttribute(String s) { - try { - JsonNode node = JacksonUtil.fromBytes(s.getBytes(StandardCharsets.UTF_8)); - if (node.isArray()) { - List entities = new ArrayList<>(); - for (int i = 0; i < node.size(); i++) { - JsonNode row = node.get(i); - UUID id = null; - String name = null; - JsonNode idNode = row.get("id"); - JsonNode nameNode = row.get("name"); - if (idNode != null && nameNode != null) { - try { - id = UUID.fromString(idNode.asText()); - } catch (Exception ignored) {} - name = nameNode.asText(); - } - if (id != null && name != null) { - entities.add(new EntityInfo(id, getEntityType().name(), name)); - } - } - return entities; - } else { - return Collections.emptyList(); - } - } catch (Exception ex) { - String exception = String.format("Failed to convert String to %s list: %s", getEntityType(), ex.getMessage()); - throw new RuntimeException(exception, ex); - } - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/WidgetBundleEntityInfosConverter.java b/dao/src/main/java/org/thingsboard/server/dao/util/mapping/WidgetBundleEntityInfosConverter.java deleted file mode 100644 index 143de09818..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/WidgetBundleEntityInfosConverter.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.util.mapping; - -import jakarta.persistence.Converter; -import org.thingsboard.server.common.data.EntityType; - -@Converter -public class WidgetBundleEntityInfosConverter extends AbstractEntityInfosConverter { - - @Override - protected EntityType getEntityType() { - return EntityType.WIDGETS_BUNDLE; - } - -} From 0b6afdc6e91b3d372fb9b7e50ac6e8da11a5f9b2 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 24 Feb 2025 14:18:36 +0200 Subject: [PATCH 8/8] Fix test --- .../server/dao/sql/widget/JpaWidgetTypeDaoTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java index 693bf86d91..27376964f5 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java @@ -21,7 +21,6 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetsBundleId; @@ -30,6 +29,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.widget.BaseWidgetType; import org.thingsboard.server.common.data.widget.DeprecatedFilter; +import org.thingsboard.server.common.data.widget.WidgetBundleInfo; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeFilter; @@ -120,8 +120,8 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { String[] tags = new String[]{"Tag1_" + number, "Tag2_" + number, "TEST_" + number}; widgetType.setTags(tags); WidgetTypeDetails saved = widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType); - List bundles = new ArrayList<>(); - bundles.add(new EntityInfo(widgetsBundle.getId(), widgetsBundle.getName())); + List bundles = new ArrayList<>(); + bundles.add(new WidgetBundleInfo(widgetsBundle.getUuidId(), widgetsBundle.getName())); return new WidgetTypeInfo(saved, bundles); }