From d052ec8a4ad5ee73698d1319acc0f1cf21cc5b2e Mon Sep 17 00:00:00 2001 From: Hoang Vu Huy Date: Mon, 23 Dec 2024 16:49:36 +0700 Subject: [PATCH 1/2] Handle feedback --- .../product-detail-information-tab.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts index 6c7a8da6..4862f8b3 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts @@ -97,7 +97,7 @@ export class ProductDetailInformationTabComponent implements OnChanges { // To ensure the function always returns a boolean, you can explicitly coerce the result into a boolean using the !! operator or default it to false // Adding !! in case of changedProduct is undefined, it will return false instead of returning undefined isProductChanged(changedProduct: SimpleChange) { - return !!( changedProduct?.previousValue && + return !!(changedProduct?.previousValue && Object.keys(changedProduct.previousValue).length > 0 && changedProduct.currentValue !== changedProduct.previousValue ); From dae15b8a45efd38eb6d79d40b16376db2d739443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20V=C4=A9nh=20Thi=E1=BB=87n=20Ph=C3=BAc?= <143604440+tvtphuc-axonivy@users.noreply.github.com> Date: Tue, 24 Dec 2024 14:00:56 +0700 Subject: [PATCH 2/2] Feature/marp 975 display compatibility range in marketplace items (#260) --- .../ProductDetailModelAssembler.java | 1 + .../market/constants/CommonConstants.java | 1 + .../com/axonivy/market/entity/Product.java | 2 + .../market/model/ProductDetailModel.java | 1 + .../service/impl/ProductServiceImpl.java | 56 ++++++++++++++++--- .../com/axonivy/market/util/VersionUtils.java | 5 ++ .../java/com/axonivy/market/BaseSetup.java | 53 ++++++++++++++++++ .../ProductDetailsControllerTest.java | 17 +----- .../service/impl/ProductServiceImplTest.java | 29 ++++++++++ ...duct-detail-information-tab.component.html | 24 ++++---- .../app/shared/models/product-detail.model.ts | 1 + 11 files changed, 154 insertions(+), 36 deletions(-) diff --git a/marketplace-service/src/main/java/com/axonivy/market/assembler/ProductDetailModelAssembler.java b/marketplace-service/src/main/java/com/axonivy/market/assembler/ProductDetailModelAssembler.java index ed4acf70..1c1ca581 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/assembler/ProductDetailModelAssembler.java +++ b/marketplace-service/src/main/java/com/axonivy/market/assembler/ProductDetailModelAssembler.java @@ -79,6 +79,7 @@ private void createDetailResource(ProductDetailModel model, Product product) { model.setContactUs(product.getContactUs()); model.setCost(product.getCost()); model.setInstallationCount(product.getInstallationCount()); + model.setCompatibilityRange(product.getCompatibilityRange()); model.setProductModuleContent(ImageUtils.mappingImageForProductModuleContent(product.getProductModuleContent())); if (StringUtils.isNotBlank(product.getVendorImage())) { Link vendorLink = linkTo(methodOn(ImageController.class).findImageById(product.getVendorImage())).withSelfRel(); diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/CommonConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/CommonConstants.java index e80c2b57..a56b5b88 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/CommonConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/CommonConstants.java @@ -19,4 +19,5 @@ public class CommonConstants { public static final String ID_WITH_NUMBER_PATTERN = "%s-%s"; public static final String ERROR = "error"; public static final String MESSAGE = "message"; + public static final String COMPATIBILITY_RANGE_FORMAT = "%s - %s"; } diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/Product.java b/marketplace-service/src/main/java/com/axonivy/market/entity/Product.java index bfe5eeac..071063fb 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/entity/Product.java +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/Product.java @@ -80,6 +80,8 @@ public class Product implements Serializable { private String bestMatchVersion; @Transient private boolean isMavenDropins; + @Transient + private String compatibilityRange; @Override public int hashCode() { diff --git a/marketplace-service/src/main/java/com/axonivy/market/model/ProductDetailModel.java b/marketplace-service/src/main/java/com/axonivy/market/model/ProductDetailModel.java index c0572fc6..7f996280 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/model/ProductDetailModel.java +++ b/marketplace-service/src/main/java/com/axonivy/market/model/ProductDetailModel.java @@ -46,6 +46,7 @@ public class ProductDetailModel extends ProductModel { private int installationCount; @Schema(description = "The api url to get metadata from product.json") private String metaProductJsonUrl; + private String compatibilityRange; private boolean isMavenDropins; @Override diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java index c26a41dd..efdf0273 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java @@ -1,7 +1,6 @@ package com.axonivy.market.service.impl; import com.axonivy.market.bo.Artifact; -import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.constants.GitHubConstants; import com.axonivy.market.constants.MavenConstants; import com.axonivy.market.constants.MetaConstants; @@ -23,6 +22,7 @@ import com.axonivy.market.github.service.GHAxonIvyProductRepoService; import com.axonivy.market.github.service.GitHubService; import com.axonivy.market.github.util.GitHubUtils; +import com.axonivy.market.model.VersionAndUrlModel; import com.axonivy.market.repository.*; import com.axonivy.market.service.ExternalDocumentService; import com.axonivy.market.service.ImageService; @@ -30,6 +30,7 @@ import com.axonivy.market.service.ProductContentService; import com.axonivy.market.service.ProductMarketplaceDataService; import com.axonivy.market.service.ProductService; +import com.axonivy.market.service.VersionService; import com.axonivy.market.util.MavenUtils; import com.axonivy.market.util.MetadataReaderUtils; import com.axonivy.market.util.VersionUtils; @@ -60,7 +61,7 @@ import java.time.format.DateTimeFormatter; import java.util.*; -import static com.axonivy.market.constants.CommonConstants.SLASH; +import static com.axonivy.market.constants.CommonConstants.*; import static com.axonivy.market.constants.MavenConstants.*; import static com.axonivy.market.constants.ProductJsonConstants.EN_LANGUAGE; import static com.axonivy.market.constants.ProductJsonConstants.LOGO_FILE; @@ -94,6 +95,7 @@ public class ProductServiceImpl implements ProductService { private final ProductMarketplaceDataService productMarketplaceDataService; private final ProductMarketplaceDataRepository productMarketplaceDataRepo; private GHCommit lastGHCommit; + private final VersionService versionService; private GitHubRepoMeta marketRepoMeta; @Value("${market.github.market.branch}") private String marketRepoBranch; @@ -106,7 +108,7 @@ public ProductServiceImpl(ProductRepository productRepo, ProductModuleContentRep MetadataSyncRepository metadataSyncRepo, MetadataRepository metadataRepo, ImageService imageService, ProductContentService productContentService, MetadataService metadataService, ProductMarketplaceDataService productMarketplaceDataService, ExternalDocumentService externalDocumentService, - ProductMarketplaceDataRepository productMarketplaceDataRepo) { + ProductMarketplaceDataRepository productMarketplaceDataRepo, VersionService versionService) { this.productRepo = productRepo; this.productModuleContentRepo = productModuleContentRepo; this.axonIvyMarketRepoService = axonIvyMarketRepoService; @@ -125,6 +127,7 @@ public ProductServiceImpl(ProductRepository productRepo, ProductModuleContentRep this.productMarketplaceDataService = productMarketplaceDataService; this.externalDocumentService = externalDocumentService; this.productMarketplaceDataRepo = productMarketplaceDataRepo; + this.versionService = versionService; } @Override @@ -191,7 +194,7 @@ private List updateLatestChangeToProductsFromGithubRepo() { Map> groupGitHubFiles = new HashMap<>(); for (var file : gitHubFileChanges) { String filePath = file.getFileName(); - var parentPath = filePath.substring(0, filePath.lastIndexOf(CommonConstants.SLASH) + 1); + var parentPath = filePath.substring(0, filePath.lastIndexOf(SLASH) + 1); var files = groupGitHubFiles.getOrDefault(parentPath, new ArrayList<>()); files.add(file); files.sort((file1, file2) -> GitHubUtils.sortMetaJsonFirst(file1.getFileName(), file2.getFileName())); @@ -582,15 +585,15 @@ public String getCompatibilityFromOldestVersion(String oldestVersion) { if (StringUtils.isBlank(oldestVersion)) { return Strings.EMPTY; } - if (!oldestVersion.contains(CommonConstants.DOT_SEPARATOR)) { + if (!oldestVersion.contains(DOT_SEPARATOR)) { return oldestVersion + ".0+"; } - int firstDot = oldestVersion.indexOf(CommonConstants.DOT_SEPARATOR); - int secondDot = oldestVersion.indexOf(CommonConstants.DOT_SEPARATOR, firstDot + 1); + int firstDot = oldestVersion.indexOf(DOT_SEPARATOR); + int secondDot = oldestVersion.indexOf(DOT_SEPARATOR, firstDot + 1); if (secondDot == -1) { - return oldestVersion.concat(CommonConstants.PLUS); + return oldestVersion.concat(PLUS); } - return oldestVersion.substring(0, secondDot).concat(CommonConstants.PLUS); + return oldestVersion.substring(0, secondDot).concat(PLUS); } @Override @@ -599,6 +602,10 @@ public Product fetchProductDetail(String id, Boolean isShowDevVersion) { return Optional.ofNullable(product).map(productItem -> { int installationCount = productMarketplaceDataService.updateProductInstallationCount(id); productItem.setInstallationCount(installationCount); + + String compatibilityRange = getCompatibilityRange(id); + productItem.setCompatibilityRange(compatibilityRange); + return productItem; }).orElse(null); } @@ -614,6 +621,10 @@ public Product fetchBestMatchProductDetail(String id, String version) { return Optional.ofNullable(product).map(productItem -> { int installationCount = productMarketplaceDataService.updateProductInstallationCount(id); productItem.setInstallationCount(installationCount); + + String compatibilityRange = getCompatibilityRange(id); + productItem.setCompatibilityRange(compatibilityRange); + productItem.setBestMatchVersion(bestMatchVersion); return productItem; }).orElse(null); @@ -749,4 +760,31 @@ public boolean syncFirstPublishedDateOfAllProducts() { return false; } } + + /** + * MARP-975: Retrieve the list containing all versions for the designer and + * split the versions to obtain the first prefix,then format them for compatibility range. + * ex: 11.0+ , 10.0 - 12.0+ , ... + */ + private String getCompatibilityRange(String productId) { + return Optional.of(versionService.getVersionsForDesigner(productId)) + .filter(ObjectUtils::isNotEmpty) + .map(versions -> versions.stream().map(VersionAndUrlModel::getVersion).toList()) + .map(versions -> { + if (versions.size() == 1) { + return splitVersion(versions.get(0)).concat(PLUS); + } + String maxVersion = splitVersion(versions.get(0)).concat(PLUS); + String minVersion = splitVersion(versions.get(versions.size() - 1)); + return VersionUtils.getPrefixOfVersion(minVersion).equals(VersionUtils.getPrefixOfVersion(maxVersion)) ? + minVersion.concat(PLUS) : String.format(COMPATIBILITY_RANGE_FORMAT, minVersion, maxVersion); + }).orElse(null); + } + + private String splitVersion(String version) { + int firstDot = version.indexOf(DOT_SEPARATOR); + int secondDot = version.indexOf(DOT_SEPARATOR, firstDot + 1); + return version.substring(0, secondDot); + } + } \ No newline at end of file diff --git a/marketplace-service/src/main/java/com/axonivy/market/util/VersionUtils.java b/marketplace-service/src/main/java/com/axonivy/market/util/VersionUtils.java index ced7b904..237aba1a 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/util/VersionUtils.java +++ b/marketplace-service/src/main/java/com/axonivy/market/util/VersionUtils.java @@ -20,6 +20,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.axonivy.market.constants.CommonConstants.DOT_SEPARATOR; import static com.axonivy.market.constants.MavenConstants.*; @Log4j2 @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -145,4 +146,8 @@ public static List getInstallableVersionsFromMetadataList(List metadata -> metadata.getVersions().stream().sorted(new LatestVersionComparator()).collect( Collectors.toList())).orElse(new ArrayList<>()); } + + public static String getPrefixOfVersion(String version) { + return version.substring(0, version.indexOf(DOT_SEPARATOR)); + } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java b/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java index 9800ebc7..87d41f0e 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java +++ b/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java @@ -12,6 +12,7 @@ import com.axonivy.market.enums.Language; import com.axonivy.market.enums.SortOption; import com.axonivy.market.model.MavenArtifactModel; +import com.axonivy.market.model.VersionAndUrlModel; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.springframework.data.domain.Page; @@ -265,4 +266,56 @@ protected static ProductJsonContent getMockProductJsonContentContainMavenDropins protected ProductMarketplaceData getMockProductMarketplaceData() { return ProductMarketplaceData.builder().id(MOCK_PRODUCT_ID).installationCount(3).build(); } + + protected List mockVersionAndUrlModels() { + VersionAndUrlModel versionAndUrlModel = VersionAndUrlModel.builder() + .version("10.0.21") + .url("/api/product-details/portal/10.0.21/json") + .build(); + + VersionAndUrlModel versionAndUrlModel2 = VersionAndUrlModel.builder() + .version("10.0.22") + .url("/api/product-details/portal/10.0.22/json") + .build(); + + return List.of(versionAndUrlModel, versionAndUrlModel2); + } + + protected List mockVersionModels() { + VersionAndUrlModel versionAndUrlModel = VersionAndUrlModel.builder() + .version("11.3.1") + .build(); + + VersionAndUrlModel versionAndUrlModel2 = VersionAndUrlModel.builder() + .version("10.0.22") + .build(); + + return List.of(versionAndUrlModel, versionAndUrlModel2); + } + + protected List mockVersionModels2() { + VersionAndUrlModel versionAndUrlModel = VersionAndUrlModel.builder() + .version("11.3.2") + .build(); + + List versionAndUrlModels = new ArrayList<>(mockVersionModels()); + versionAndUrlModels.add(0,versionAndUrlModel); + + return versionAndUrlModels; + } + + protected List mockVersionModels3() { + VersionAndUrlModel versionAndUrlModel = VersionAndUrlModel.builder() + .version("11.3.2") + .build(); + + VersionAndUrlModel versionAndUrlModel2 = VersionAndUrlModel.builder() + .version("12.0.0") + .build(); + + List versionAndUrlModels = new ArrayList<>(mockVersionModels()); + versionAndUrlModels.add(0,versionAndUrlModel); + versionAndUrlModels.add(0,versionAndUrlModel2); + return versionAndUrlModels; + } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java index 249df32a..812f4fc4 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java @@ -8,7 +8,6 @@ import com.axonivy.market.enums.Language; import com.axonivy.market.model.MavenArtifactVersionModel; import com.axonivy.market.model.ProductDetailModel; -import com.axonivy.market.model.VersionAndUrlModel; import com.axonivy.market.service.ProductService; import com.axonivy.market.service.VersionService; import com.fasterxml.jackson.databind.ObjectMapper; @@ -170,26 +169,14 @@ void findProductVersionsById() { assertEquals(2, Objects.requireNonNull(result.getBody()).size()); assertEquals("10.0.21", Objects.requireNonNull(result.getBody()).get(0).getVersion()); - assertEquals("/api/product-details/productjsoncontent/portal/10.0.21", + assertEquals("/api/product-details/portal/10.0.21/json", Objects.requireNonNull(result.getBody()).get(0).getUrl()); assertEquals("10.0.22", Objects.requireNonNull(result.getBody()).get(1).getVersion()); - assertEquals("/api/product-details/productjsoncontent/portal/10.0.22", + assertEquals("/api/product-details/portal/10.0.22/json", Objects.requireNonNull(result.getBody()).get(1).getUrl()); } - private List mockVersionAndUrlModels() { - VersionAndUrlModel versionAndUrlModel = VersionAndUrlModel.builder() - .version("10.0.21") - .url("/api/product-details/productjsoncontent/portal/10.0.21") - .build(); - VersionAndUrlModel versionAndUrlModel2 = VersionAndUrlModel.builder() - .version("10.0.22") - .url("/api/product-details/productjsoncontent/portal/10.0.22") - .build(); - - return List.of(versionAndUrlModel, versionAndUrlModel2); - } @Test void findProductJsonContentByIdAndVersion() throws IOException { diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java index 7896e389..1138fa27 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java @@ -33,6 +33,7 @@ import com.axonivy.market.service.MetadataService; import com.axonivy.market.service.ProductContentService; import com.axonivy.market.service.ProductMarketplaceDataService; +import com.axonivy.market.service.VersionService; import com.axonivy.market.util.MavenUtils; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; @@ -129,6 +130,8 @@ class ProductServiceImplTest extends BaseSetup { private ProductMarketplaceDataService productMarketplaceDataService; @Mock private ProductMarketplaceDataRepository productMarketplaceDataRepo; + @Mock + private VersionService versionService; @InjectMocks private ProductServiceImpl productService; @@ -401,6 +404,32 @@ void testFetchProductDetail() { assertNull(result); } + @Test + void testGetCompatibilityRangeAfterFetchProductDetail() { + MavenArtifactVersion mockMavenArtifactVersion = getMockMavenArtifactVersionWithData(); + when(mavenArtifactVersionRepo.findById(MOCK_PRODUCT_ID)).thenReturn( + Optional.ofNullable(mockMavenArtifactVersion)); + + + when(productRepo.getProductByIdAndVersion(MOCK_PRODUCT_ID, MOCK_SNAPSHOT_VERSION)) + .thenReturn(getMockProduct()); + when(versionService.getVersionsForDesigner(MOCK_PRODUCT_ID)) + .thenReturn(mockVersionAndUrlModels(), mockVersionModels(), mockVersionModels2(), mockVersionModels3()); + + + Product result = productService.fetchProductDetail(MOCK_PRODUCT_ID, true); + assertEquals("10.0+", result.getCompatibilityRange()); + + result = productService.fetchProductDetail(MOCK_PRODUCT_ID, true); + assertEquals("10.0 - 11.3+", result.getCompatibilityRange()); + + result = productService.fetchProductDetail(MOCK_PRODUCT_ID, true); + assertEquals("10.0 - 11.3+", result.getCompatibilityRange()); + + result = productService.fetchProductDetail(MOCK_PRODUCT_ID, true); + assertEquals("10.0 - 12.0+", result.getCompatibilityRange()); + } + @Test void testGetProductByIdWithNewestReleaseVersion() { MavenArtifactVersion mockMavenArtifactVersion = getMockMavenArtifactVersionWithData(); diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.html b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.html index 9aca7f03..cc163f09 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.html +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.html @@ -23,16 +23,7 @@ alt="Logo Vendor" /> -
-
- - {{ 'common.product.detail.information.value.version' | translate }} - - - {{ displayVersion }} - -
- @if (productDetail.compatibility) { + @if (productDetail.compatibilityRange) {
- {{ productDetail.compatibility }} + {{ productDetail.compatibilityRange }}
} @@ -77,7 +68,7 @@ target="_blank"> {{ displayExternalDocName ?? - 'common.product.detail.information.value.defaultDocName' + 'common.product.detail.information.value.defaultDocName' | translate }} @@ -91,6 +82,15 @@ {{ productDetail.type }}
+
+ + {{ 'common.product.detail.information.value.version' | translate }} + + + {{ displayVersion }} + +
+
{{ 'common.product.detail.information.value.tag' | translate }} diff --git a/marketplace-ui/src/app/shared/models/product-detail.model.ts b/marketplace-ui/src/app/shared/models/product-detail.model.ts index 145cb144..39542b43 100644 --- a/marketplace-ui/src/app/shared/models/product-detail.model.ts +++ b/marketplace-ui/src/app/shared/models/product-detail.model.ts @@ -25,6 +25,7 @@ export interface ProductDetail { productModuleContent: ProductModuleContent; mavenDropins: boolean; metaProductJsonUrl?: string; + compatibilityRange?: string; _links: { self: { href: string;