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/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 1ce8ad6ddc..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 @@ -16,13 +16,21 @@ 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.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 +42,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(); @@ -48,14 +59,23 @@ public class WidgetTypeInfo extends BaseWidgetType { } public WidgetTypeInfo(WidgetTypeInfo widgetTypeInfo) { + this(widgetTypeInfo, 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) { + this(widgetTypeDetails, Collections.emptyList()); + } + + public WidgetTypeInfo(WidgetTypeDetails widgetTypeDetails, List bundles) { super(widgetTypeDetails); this.image = widgetTypeDetails.getImage(); this.description = widgetTypeDetails.getDescription(); @@ -65,5 +85,7 @@ public class WidgetTypeInfo extends BaseWidgetType { } else { this.widgetType = ""; } + this.bundles = bundles; } + } 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..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,17 +15,22 @@ */ 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; 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.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.JsonConverter; import java.util.HashMap; import java.util.Map; @@ -58,6 +63,10 @@ public class WidgetTypeInfoEntity extends AbstractWidgetTypeEntity() {})); return widgetTypeInfo; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java index 6220d9b717..4a656c3374 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java @@ -84,6 +84,11 @@ public class JpaWidgetTypeDao extends JpaAbstractDao findSystemWidgetTypes(WidgetTypeFilter widgetTypeFilter, PageLink pageLink) { boolean deprecatedFilterEnabled = !DeprecatedFilter.ALL.equals(widgetTypeFilter.getDeprecatedFilter()); 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, 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..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 @@ -29,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; @@ -49,11 +50,10 @@ 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; -/** - * 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 +78,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 +107,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); @@ -117,9 +117,12 @@ 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); - return widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType); + WidgetTypeDetails saved = widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType); + List bundles = new ArrayList<>(); + bundles.add(new WidgetBundleInfo(widgetsBundle.getUuidId(), widgetsBundle.getName())); + return new WidgetTypeInfo(saved, bundles); } WidgetTypeDetails createAndSaveWidgetType(TenantId tenantId, int number, String[] tags) { @@ -139,7 +142,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 +163,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 +173,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,17 +184,17 @@ 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()); 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)); @@ -216,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) ); @@ -254,12 +257,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(), @@ -360,16 +363,16 @@ 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(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 +413,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()); @@ -426,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); @@ -437,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); } + } 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..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 @@ -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 {