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 ede1c0aba..8817c5929 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 @@ -80,6 +80,6 @@ private void createDetailResource(ProductDetailModel model, Product product) { model.setContactUs(product.getContactUs()); model.setCost(product.getCost()); model.setInstallationCount(product.getInstallationCount()); - model.setProductModuleContent(CollectionUtils.firstElement(product.getProductModuleContents())); + model.setProductModuleContent(product.getProductModuleContent()); } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/EntityConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/EntityConstants.java index edbf9ac4a..fc813d23a 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/EntityConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/EntityConstants.java @@ -12,4 +12,5 @@ public class EntityConstants { public static final String FEEDBACK = "Feedback"; public static final String PRODUCT_CUSTOM_SORT = "ProductCustomSort"; public static final String PRODUCT_JSON_CONTENT = "ProductJsonContent"; + public static final String PRODUCT_MODULE_CONTENT = "ProductModuleContent"; } diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/MongoDBConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/MongoDBConstants.java index 2915af56a..df9c3c1e0 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/MongoDBConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/MongoDBConstants.java @@ -1,20 +1,20 @@ package com.axonivy.market.constants; public class MongoDBConstants { + private MongoDBConstants() { } - + public static final String ID ="_id"; - public static final String ADD_FIELD ="$addFields"; - public static final String PRODUCT_MODULE_CONTENTS ="productModuleContents"; public static final String PRODUCT_MODULE_CONTENT ="productModuleContent"; public static final String PRODUCT_MODULE_CONTENT_QUERY ="$productModuleContents"; - public static final String FILTER ="$filter"; public static final String INPUT ="input"; public static final String AS ="as"; public static final String CONDITION ="cond"; public static final String EQUAL ="$eq"; public static final String PRODUCT_MODULE_CONTENT_TAG ="$$productModuleContent.tag"; public static final String PRODUCT_COLLECTION ="Product"; - public static final String NEWEST_RELEASED_VERSION_QUERY = "$newestReleaseVersion"; + public static final String INSTALLATION_COUNT = "InstallationCount"; + public static final String SYNCHRONIZED_INSTALLATION_COUNT ="SynchronizedInstallationCount"; + } diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java index d28e4193e..7c4efc906 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java @@ -80,7 +80,7 @@ public ResponseEntity syncInstallationCount( } @GetMapping(BY_ID) - @Operation(summary = "increase installation count by 1", description = "update installation count when click download product files by users") + @Operation(summary = "get product detail by ID", description = "Return product detail by product id (from meta.json)") public ResponseEntity findProductDetails( @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String id) { var productDetail = productService.fetchProductDetail(id); 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 ddbec5b50..e11d7fba8 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 @@ -11,6 +11,7 @@ import org.apache.commons.lang3.builder.HashCodeBuilder; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient; +import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; import java.io.Serial; @@ -57,12 +58,12 @@ public class Product implements Serializable { private int installationCount; private Date newestPublishedDate; private String newestReleaseVersion; - private List productModuleContents; + @Transient + private ProductModuleContent productModuleContent; private List artifacts; private Boolean synchronizedInstallationCount; private Integer customOrder; private List releasedVersions; - @Transient private String metaProductJsonUrl; diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/ProductModuleContent.java b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductModuleContent.java index aacbe17bd..4001571d1 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/entity/ProductModuleContent.java +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductModuleContent.java @@ -2,21 +2,32 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; import java.io.Serial; import java.io.Serializable; import java.util.Map; +import static com.axonivy.market.constants.EntityConstants.PRODUCT_MODULE_CONTENT; + @Getter @Setter @NoArgsConstructor @AllArgsConstructor +@Builder +@Document(PRODUCT_MODULE_CONTENT) public class ProductModuleContent implements Serializable { + @Id + private String id; @Serial private static final long serialVersionUID = 1L; + @Schema(description = "product Id (from meta.json)", example = "portal") + private String productId; @Schema(description = "Target release tag", example = "v10.0.25") private String tag; @Schema(description = "Product detail description content ", example = "{ \"de\": \"E-Sign-Konnektor\", \"en\": \"E-sign connector\" }") diff --git a/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java index a99b4c93f..e1843ae25 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java @@ -165,6 +165,7 @@ public ProductModuleContent getReadmeAndProductContentsFromTag(Product product, ProductModuleContent productModuleContent = new ProductModuleContent(); try { List contents = getProductFolderContents(product, ghRepository, tag); + productModuleContent.setProductId(product.getId()); productModuleContent.setTag(tag); updateDependencyContentsFromProductJson(productModuleContent, contents , product); List readmeFiles = contents.stream().filter(GHContent::isFile) diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java new file mode 100644 index 000000000..75bdc53db --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java @@ -0,0 +1,10 @@ +package com.axonivy.market.repository; + +import com.axonivy.market.entity.ProductModuleContent; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProductModuleContentRepository extends MongoRepository { + ProductModuleContent findByTagAndProductId(String tag, String productId); +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductRepositoryImpl.java b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductRepositoryImpl.java index c43502185..34a538b15 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductRepositoryImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductRepositoryImpl.java @@ -2,8 +2,10 @@ import com.axonivy.market.constants.MongoDBConstants; import com.axonivy.market.entity.Product; +import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.repository.CustomProductRepository; -import org.bson.Document; +import com.axonivy.market.repository.ProductModuleContentRepository; +import lombok.Builder; import org.springframework.data.mongodb.core.FindAndModifyOptions; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; @@ -13,45 +15,25 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; - +@Builder public class CustomProductRepositoryImpl implements CustomProductRepository { private final MongoTemplate mongoTemplate; + private final ProductModuleContentRepository contentRepository; - public CustomProductRepositoryImpl(MongoTemplate mongoTemplate) { + public CustomProductRepositoryImpl(MongoTemplate mongoTemplate, ProductModuleContentRepository contentRepository) { this.mongoTemplate = mongoTemplate; + this.contentRepository = contentRepository; } private AggregationOperation createIdMatchOperation(String id) { return Aggregation.match(Criteria.where(MongoDBConstants.ID).is(id)); } - public Document createDocumentFilterProductModuleContentByTag(String tag) { - Document isProductModuleContentOfCurrentTag = new Document(MongoDBConstants.EQUAL, - Arrays.asList(MongoDBConstants.PRODUCT_MODULE_CONTENT_TAG, tag)); - Document loopOverProductModuleContents = new Document(MongoDBConstants.INPUT, - MongoDBConstants.PRODUCT_MODULE_CONTENT_QUERY) - .append(MongoDBConstants.AS, MongoDBConstants.PRODUCT_MODULE_CONTENT); - return loopOverProductModuleContents.append(MongoDBConstants.CONDITION, isProductModuleContentOfCurrentTag); - } - - private AggregationOperation createReturnFirstModuleContentOperation() { - return context -> new Document(MongoDBConstants.ADD_FIELD, - new Document(MongoDBConstants.PRODUCT_MODULE_CONTENTS, - new Document(MongoDBConstants.FILTER, createDocumentFilterProductModuleContentByTag(MongoDBConstants.NEWEST_RELEASED_VERSION_QUERY)))); - } - - private AggregationOperation createReturnFirstMatchTagModuleContentOperation(String tag) { - return context -> new Document(MongoDBConstants.ADD_FIELD, - new Document(MongoDBConstants.PRODUCT_MODULE_CONTENTS, - new Document(MongoDBConstants.FILTER, createDocumentFilterProductModuleContentByTag(tag)))); - } - public Product queryProductByAggregation(Aggregation aggregation) { return Optional.of(mongoTemplate.aggregate(aggregation, MongoDBConstants.PRODUCT_COLLECTION, Product.class)) .map(AggregationResults::getUniqueMappedResult).orElse(null); @@ -59,15 +41,28 @@ public Product queryProductByAggregation(Aggregation aggregation) { @Override public Product getProductByIdAndTag(String id, String tag) { - // Create the aggregation pipeline - Aggregation aggregation = Aggregation.newAggregation(createIdMatchOperation(id), createReturnFirstMatchTagModuleContentOperation(tag)); + Product result = findProductById(id); + if (!Objects.isNull(result)) { + ProductModuleContent content = contentRepository.findByTagAndProductId(tag,id); + result.setProductModuleContent(content); + } + return result; + } + + private Product findProductById(String id) { + Aggregation aggregation = Aggregation.newAggregation(createIdMatchOperation(id)); return queryProductByAggregation(aggregation); } @Override public Product getProductById(String id) { - Aggregation aggregation = Aggregation.newAggregation(createIdMatchOperation(id), createReturnFirstModuleContentOperation()); - return queryProductByAggregation(aggregation); + Product result = findProductById(id); + if (!Objects.isNull(result)) { + ProductModuleContent content = contentRepository.findByTagAndProductId( + result.getNewestReleaseVersion(), id); + result.setProductModuleContent(content); + } + return result; } @Override @@ -78,19 +73,17 @@ public List getReleasedVersionsById(String id) { return Collections.emptyList(); } return product.getReleasedVersions(); - } public int updateInitialCount(String productId, int initialCount) { - Update update = new Update().inc("InstallationCount", initialCount).set("SynchronizedInstallationCount", true); + Update update = new Update().inc(MongoDBConstants.INSTALLATION_COUNT, initialCount).set(MongoDBConstants.SYNCHRONIZED_INSTALLATION_COUNT, true); mongoTemplate.updateFirst(createQueryById(productId), update, Product.class); return Optional.ofNullable(getProductById(productId)).map(Product::getInstallationCount).orElse(0); } @Override public int increaseInstallationCount(String productId) { - Update update = new Update().inc("InstallationCount", 1); - // Find and modify the document, then return the updated InstallationCount field + Update update = new Update().inc(MongoDBConstants.INSTALLATION_COUNT, 1); Product updatedProduct = mongoTemplate.findAndModify(createQueryById(productId), update, FindAndModifyOptions.options().returnNew(true), Product.class); return updatedProduct != null ? updatedProduct.getInstallationCount() : 0; 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 6c57569ae..7e0a41a9d 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,44 +1,5 @@ package com.axonivy.market.service.impl; -import static com.axonivy.market.enums.DocumentField.MARKET_DIRECTORY; -import static com.axonivy.market.enums.DocumentField.SHORT_DESCRIPTIONS; - -import static java.util.Optional.ofNullable; -import static org.apache.commons.lang3.StringUtils.EMPTY; - -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import com.axonivy.market.util.VersionUtils; -import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.util.Strings; -import org.kohsuke.github.GHCommit; -import org.kohsuke.github.GHContent; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHTag; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.domain.Sort.Order; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; -import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; - import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.constants.GitHubConstants; import com.axonivy.market.constants.ProductJsonConstants; @@ -47,11 +8,7 @@ import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductCustomSort; import com.axonivy.market.entity.ProductModuleContent; -import com.axonivy.market.enums.ErrorCode; -import com.axonivy.market.enums.FileType; -import com.axonivy.market.enums.Language; -import com.axonivy.market.enums.SortOption; -import com.axonivy.market.enums.TypeOption; +import com.axonivy.market.enums.*; import com.axonivy.market.exceptions.model.InvalidParamException; import com.axonivy.market.factory.ProductFactory; import com.axonivy.market.github.model.GitHubFile; @@ -62,18 +19,51 @@ import com.axonivy.market.model.ProductCustomSortRequest; import com.axonivy.market.repository.GitHubRepoMetaRepository; import com.axonivy.market.repository.ProductCustomSortRepository; +import com.axonivy.market.repository.ProductModuleContentRepository; import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.service.ProductService; +import com.axonivy.market.util.VersionUtils; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; - import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.util.Strings; +import org.kohsuke.github.GHCommit; +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHTag; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Order; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.SecureRandom; +import java.util.*; + +import static com.axonivy.market.enums.DocumentField.MARKET_DIRECTORY; +import static com.axonivy.market.enums.DocumentField.SHORT_DESCRIPTIONS; +import static java.util.Optional.ofNullable; +import static org.apache.commons.lang3.StringUtils.EMPTY; @Log4j2 @Service public class ProductServiceImpl implements ProductService { private final ProductRepository productRepository; + private final ProductModuleContentRepository productModuleContentRepository; private final GHAxonIvyMarketRepoService axonIvyMarketRepoService; private final GHAxonIvyProductRepoService axonIvyProductRepoService; private final GitHubRepoMetaRepository gitHubRepoMetaRepository; @@ -95,11 +85,14 @@ public class ProductServiceImpl implements ProductService { public static final String NON_NUMERIC_CHAR = "[^0-9.]"; private final SecureRandom random = new SecureRandom(); - public ProductServiceImpl(ProductRepository productRepository, GHAxonIvyMarketRepoService axonIvyMarketRepoService, + public ProductServiceImpl(ProductRepository productRepository, + ProductModuleContentRepository productModuleContentRepository, + GHAxonIvyMarketRepoService axonIvyMarketRepoService, GHAxonIvyProductRepoService axonIvyProductRepoService, GitHubRepoMetaRepository gitHubRepoMetaRepository, GitHubService gitHubService, ProductCustomSortRepository productCustomSortRepository, MongoTemplate mongoTemplate) { this.productRepository = productRepository; + this.productModuleContentRepository = productModuleContentRepository; this.axonIvyMarketRepoService = axonIvyMarketRepoService; this.axonIvyProductRepoService = axonIvyProductRepoService; this.gitHubRepoMetaRepository = gitHubRepoMetaRepository; @@ -350,34 +343,37 @@ private void getProductContents(Product product) { private void updateProductFromReleaseTags(Product product, GHRepository productRepo) { List productModuleContents = new ArrayList<>(); - List tags = getProductReleaseTags(product); - GHTag lastTag = CollectionUtils.firstElement(tags); + List ghTags = getProductReleaseTags(product); + GHTag lastTag = CollectionUtils.firstElement(ghTags); if (lastTag == null || lastTag.getName().equals(product.getNewestReleaseVersion())) { return; } - getPublishedDateFromLatestTag(product, lastTag); + getPublishedDateFromLatestTag(product, + lastTag); product.setNewestReleaseVersion(lastTag.getName()); - if (!ObjectUtils.isEmpty(product.getProductModuleContents())) { - productModuleContents.addAll(product.getProductModuleContents()); - List currentTags = product.getProductModuleContents().stream().filter(Objects::nonNull) - .map(ProductModuleContent::getTag).toList(); - tags = tags.stream().filter(t -> !currentTags.contains(t.getName())).toList(); + if (!CollectionUtils.isEmpty(product.getReleasedVersions())) { + List currentTags = VersionUtils.getReleaseTagsFromProduct(product); + ghTags = ghTags.stream().filter(t -> !currentTags.contains(t.getName())).toList(); } - for (GHTag ghTag : tags) { + for (GHTag ghTag : ghTags) { ProductModuleContent productModuleContent = axonIvyProductRepoService.getReadmeAndProductContentsFromTag(product, productRepo, ghTag.getName()); - productModuleContents.add(productModuleContent); + if (productModuleContent != null) { + productModuleContents.add(productModuleContent); + } String versionFromTag = VersionUtils.convertTagToVersion(ghTag.getName()); if (Objects.isNull(product.getReleasedVersions())) { product.setReleasedVersions(new ArrayList<>()); } product.getReleasedVersions().add(versionFromTag); } - product.setProductModuleContents(productModuleContents); + if (!CollectionUtils.isEmpty(productModuleContents)) { + productModuleContentRepository.saveAll(productModuleContents); + } } private void getPublishedDateFromLatestTag(Product product, GHTag lastTag) { @@ -437,7 +433,6 @@ public Product fetchProductDetail(String id) { }).orElse(null); } - @Override public Product fetchBestMatchProductDetail(String id, String version) { List releasedVersions = productRepository.getReleasedVersionsById(id); 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 ebfac0d7a..dc580a9c4 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 @@ -4,13 +4,16 @@ import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.constants.GitHubConstants; import com.axonivy.market.constants.MavenConstants; +import com.axonivy.market.entity.Product; import com.axonivy.market.enums.NonStandardProduct; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.util.CollectionUtils; +import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.stream.Stream; public class VersionUtils { @@ -109,4 +112,10 @@ public static String convertVersionToTag(String productId, String version) { return GitHubConstants.STANDARD_TAG_PREFIX.concat(version); } + public static List getReleaseTagsFromProduct(Product product) { + if (Objects.isNull(product)) { + return new ArrayList<>(); + } + return product.getReleasedVersions().stream().map(version -> convertVersionToTag(product.getId(), version)).toList(); + } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/repository/impl/CustomProductRepositoryImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/repository/impl/CustomProductRepositoryImplTest.java index 671690f41..4d9fb26db 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/repository/impl/CustomProductRepositoryImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/repository/impl/CustomProductRepositoryImplTest.java @@ -3,7 +3,7 @@ import com.axonivy.market.BaseSetup; import com.axonivy.market.constants.MongoDBConstants; import com.axonivy.market.entity.Product; -import org.bson.Document; +import com.axonivy.market.repository.ProductModuleContentRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -16,7 +16,6 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; -import java.util.Arrays; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -35,6 +34,9 @@ class CustomProductRepositoryImplTest extends BaseSetup { private Product mockProduct; private Aggregation mockAggregation; + @Mock + ProductModuleContentRepository contentRepo; + @Mock private MongoTemplate mongoTemplate; @@ -92,23 +94,11 @@ void testGetProductById() { @Test void testGetProductByIdAndTag() { setUpMockAggregateResult(); + when(contentRepo.findByTagAndProductId(TAG, ID)).thenReturn(null); Product actualProduct = repo.getProductByIdAndTag(ID, TAG); assertEquals(mockProduct, actualProduct); } - @Test - void testCreateDocumentFilterProductModuleContentByTag() { - Document expectedCondition = new Document(MongoDBConstants.EQUAL, - Arrays.asList(MongoDBConstants.PRODUCT_MODULE_CONTENT_TAG, TAG)); - Document expectedLoop = new Document(MongoDBConstants.INPUT, MongoDBConstants.PRODUCT_MODULE_CONTENT_QUERY) - .append(MongoDBConstants.AS, MongoDBConstants.PRODUCT_MODULE_CONTENT) - .append(MongoDBConstants.CONDITION, expectedCondition); - - Document result = repo.createDocumentFilterProductModuleContentByTag(TAG); - - assertEquals(expectedLoop, result, "The created Document does not match the expected structure."); - } - @Test void testGetReleasedVersionsById() { setUpMockAggregateResult(); 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 28328b2e2..127eaa577 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 @@ -12,6 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -75,6 +76,7 @@ import com.axonivy.market.model.ProductCustomSortRequest; import com.axonivy.market.repository.GitHubRepoMetaRepository; import com.axonivy.market.repository.ProductCustomSortRepository; +import com.axonivy.market.repository.ProductModuleContentRepository; import com.axonivy.market.repository.ProductRepository; @ExtendWith(MockitoExtension.class) @@ -100,6 +102,9 @@ class ProductServiceImplTest extends BaseSetup { @Mock private ProductRepository productRepository; + @Mock + private ProductModuleContentRepository productModuleContentRepository; + @Mock private GHAxonIvyMarketRepoService marketRepoService; @@ -114,6 +119,10 @@ class ProductServiceImplTest extends BaseSetup { @Captor ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Product.class); + + @Captor + ArgumentCaptor> argumentCaptorProductModuleContent; + @Mock private GHAxonIvyProductRepoService ghAxonIvyProductRepoService; @@ -140,7 +149,7 @@ void testUpdateInstallationCountForProduct() { when(productRepository.getProductById(product.getId())).thenReturn(product); when(productRepository.increaseInstallationCount(product.getId())).thenReturn(31); result = productService.updateInstallationCountForProduct(product.getId()); - assertEquals(31,result); + assertEquals(31, result); } @Test @@ -289,8 +298,8 @@ void testFindAllProductsWithKeyword() { && product.getType().equals(TypeOption.CONNECTORS.getCode())) .toList())); // Executes - result = - productService.findProducts(TypeOption.CONNECTORS.getOption(), SAMPLE_PRODUCT_NAME, language, false, PAGEABLE); + result = productService.findProducts(TypeOption.CONNECTORS.getOption(), SAMPLE_PRODUCT_NAME, language, false, + PAGEABLE); assertTrue(result.hasContent()); assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getNames().get(Language.EN.getValue())); } @@ -315,17 +324,17 @@ void testSyncProductsFirstTime() throws IOException { var mockContent = mockGHContentAsMetaJSON(); InputStream inputStream = this.getClass().getResourceAsStream(SLASH.concat(META_FILE)); when(mockContent.read()).thenReturn(inputStream); - Map> mockGHContentMap = new HashMap<>(); mockGHContentMap.put(SAMPLE_PRODUCT_ID, List.of(mockContent)); when(marketRepoService.fetchAllMarketItems()).thenReturn(mockGHContentMap); + when(productModuleContentRepository.saveAll(anyList())).thenReturn(List.of(mockReadmeProductContent())); // Executes productService.syncLatestDataFromMarketRepo(); - + verify(productModuleContentRepository).saveAll(argumentCaptorProductModuleContent.capture()); verify(productRepository).save(argumentCaptor.capture()); - assertThat(argumentCaptor.getValue().getProductModuleContents()).usingRecursiveComparison() + assertThat(argumentCaptorProductModuleContent.getValue()).usingRecursiveComparison() .isEqualTo(List.of(mockReadmeProductContent())); } @@ -356,14 +365,16 @@ void testSyncProductsSecondTime() throws IOException { when(ghAxonIvyProductRepoService.getReadmeAndProductContentsFromTag(any(), any(), anyString())) .thenReturn(mockReturnProductContent); + when(productModuleContentRepository.saveAll(anyList())) + .thenReturn(List.of(mockReadmeProductContent(), mockReturnProductContent)); // Executes productService.syncLatestDataFromMarketRepo(); + verify(productModuleContentRepository).saveAll(argumentCaptorProductModuleContent.capture()); verify(productRepository).save(argumentCaptor.capture()); - assertEquals(2, argumentCaptor.getValue().getProductModuleContents().size()); - assertThat(argumentCaptor.getValue().getProductModuleContents()).usingRecursiveComparison() - .isEqualTo(List.of(mockReadmeProductContent(), mockReturnProductContent)); + assertThat(argumentCaptor.getValue().getProductModuleContent()).usingRecursiveComparison() + .isEqualTo(mockReadmeProductContent()); } @Test @@ -379,6 +390,23 @@ void testNothingToSync() { assertTrue(result); } + @Test + void testSyncNullProductModuleContent() { + var mockCommit = mockGHCommitHasSHA1(SHA1_SAMPLE); + when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit); + when(repoMetaRepository.findByRepoName(anyString())).thenReturn(null); + + Map> mockGHContentMap = new HashMap<>(); + mockGHContentMap.put(SAMPLE_PRODUCT_ID, new ArrayList<>()); + when(marketRepoService.fetchAllMarketItems()).thenReturn(mockGHContentMap); + + // Executes + productService.syncLatestDataFromMarketRepo(); + verify(productRepository).save(argumentCaptor.capture()); + + assertThat(argumentCaptor.getValue().getProductModuleContent()).isNull(); + } + @Test void testSearchProducts() { var simplePageable = PageRequest.of(0, 20); @@ -466,8 +494,8 @@ void testRefineOrderedListOfProductsInCustomSort_ProductNotFound() { List orderedListOfProducts = List.of(SAMPLE_PRODUCT_ID); when(productRepository.findById(SAMPLE_PRODUCT_ID)).thenReturn(Optional.empty()); - InvalidParamException exception = assertThrows(InvalidParamException.class, () -> - productService.refineOrderedListOfProductsInCustomSort(orderedListOfProducts)); + InvalidParamException exception = assertThrows(InvalidParamException.class, + () -> productService.refineOrderedListOfProductsInCustomSort(orderedListOfProducts)); assertEquals(ErrorCode.PRODUCT_NOT_FOUND.getCode(), exception.getCode()); } @@ -547,6 +575,7 @@ private GHContent mockGHContentAsMetaJSON() { private ProductModuleContent mockReadmeProductContent() { ProductModuleContent productModuleContent = new ProductModuleContent(); + productModuleContent.setId("123"); productModuleContent.setTag("v10.0.2"); productModuleContent.setName("Amazon Comprehend"); Map description = new HashMap<>(); @@ -557,7 +586,7 @@ private ProductModuleContent mockReadmeProductContent() { private List mockProducts() { Product product1 = Product.builder().repositoryName("axonivy-market/amazon-comprehend-connector") - .productModuleContents(List.of(mockReadmeProductContent())).build(); + .productModuleContent(mockReadmeProductContent()).build(); return List.of(product1); } } diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.html b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.html index 80be049b7..434725085 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.html +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.html @@ -87,7 +87,7 @@