From 3f0cbdb32020a24b08a729d8146915075dd0a0ce Mon Sep 17 00:00:00 2001 From: tutn Date: Thu, 22 Aug 2024 11:17:51 +0700 Subject: [PATCH 01/21] Update save product module content to other db --- .../market/constants/EntityConstants.java | 1 + .../market/constants/MongoDBConstants.java | 14 ++++++++- .../com/axonivy/market/entity/Product.java | 2 ++ .../market/entity/ProductModuleContent.java | 10 +++++++ .../ProductModuleContentRepository.java | 9 ++++++ .../impl/CustomProductRepositoryImpl.java | 29 +++++++++++++++---- .../service/impl/ProductServiceImpl.java | 23 +++++++++++---- 7 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java 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 048f49956..415b35933 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 @@ -11,4 +11,5 @@ public class EntityConstants { public static final String GH_REPO_META = "GitHubRepoMeta"; public static final String FEEDBACK = "Feedback"; public static final String PRODUCT_CUSTOM_SORT = "ProductCustomSort"; + 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..d85e5554f 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,12 +1,16 @@ package com.axonivy.market.constants; +import java.util.Map; + 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_CONTENTS_KEY_FIELD ="productModuleContents.$id"; public static final String PRODUCT_MODULE_CONTENT ="productModuleContent"; public static final String PRODUCT_MODULE_CONTENT_QUERY ="$productModuleContents"; public static final String FILTER ="$filter"; @@ -15,6 +19,14 @@ private MongoDBConstants() { 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_MODULE_CONTENT_FILTER_TAG ="productModuleContent.tag"; public static final String PRODUCT_COLLECTION ="Product"; public static final String NEWEST_RELEASED_VERSION_QUERY = "$newestReleaseVersion"; + public static final String PRODUCT_MODULE_CONTENT_DOCUMENT ="ProductModuleContent"; + public static final String UNWIND = "$unwind"; + public static final String LOOKUP = "$lookup"; + public static final String MATCH = "$match"; + public static final String FROM = "from"; + public static final String LOCAL_FIELD = "localField"; + public static final String FOREIGN_FIELD = "foreignField"; } 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 6cd687032..8567d811c 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 @@ -10,6 +10,7 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; import java.io.Serial; @@ -56,6 +57,7 @@ public class Product implements Serializable { private int installationCount; private Date newestPublishedDate; private String newestReleaseVersion; + @DBRef(lazy = false) private List productModuleContents; private List artifacts; private Boolean synchronizedInstallationCount; 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..ba901fa90 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,19 +2,29 @@ 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.DBRef; +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 = "Target release tag", example = "v10.0.25") 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..f48ccce38 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java @@ -0,0 +1,9 @@ +package com.axonivy.market.repository; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import com.axonivy.market.entity.ProductModuleContent; + +@Repository +public interface ProductModuleContentRepository extends MongoRepository {} 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..aa7114bcc 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 @@ -40,9 +40,20 @@ public Document createDocumentFilterProductModuleContentByTag(String tag) { return loopOverProductModuleContents.append(MongoDBConstants.CONDITION, isProductModuleContentOfCurrentTag); } - private AggregationOperation createReturnFirstModuleContentOperation() { - return context -> new Document(MongoDBConstants.ADD_FIELD, - new Document(MongoDBConstants.PRODUCT_MODULE_CONTENTS, + + private AggregationOperation createLookupProductModuleContentOperation() { + return context -> new Document(MongoDBConstants.LOOKUP, + new Document() + .append(MongoDBConstants.FROM, MongoDBConstants.PRODUCT_MODULE_CONTENT_DOCUMENT) + .append(MongoDBConstants.LOCAL_FIELD, MongoDBConstants.PRODUCT_MODULE_CONTENTS_KEY_FIELD) + .append(MongoDBConstants.FOREIGN_FIELD, MongoDBConstants.ID) + .append(MongoDBConstants.AS, MongoDBConstants.PRODUCT_MODULE_CONTENTS)); + } + + private AggregationOperation createAddFieldProductModuleContentOperation() { + return context -> + new Document(MongoDBConstants.ADD_FIELD, + new Document(MongoDBConstants.PRODUCT_MODULE_CONTENTS, new Document(MongoDBConstants.FILTER, createDocumentFilterProductModuleContentByTag(MongoDBConstants.NEWEST_RELEASED_VERSION_QUERY)))); } @@ -60,13 +71,20 @@ 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)); + Aggregation aggregation = Aggregation.newAggregation( + createIdMatchOperation(id), + createLookupProductModuleContentOperation(), + createReturnFirstMatchTagModuleContentOperation(tag)); return queryProductByAggregation(aggregation); } @Override public Product getProductById(String id) { - Aggregation aggregation = Aggregation.newAggregation(createIdMatchOperation(id), createReturnFirstModuleContentOperation()); + Aggregation aggregation = Aggregation.newAggregation( + createIdMatchOperation(id), + createLookupProductModuleContentOperation(), + createAddFieldProductModuleContentOperation() + ); return queryProductByAggregation(aggregation); } @@ -78,7 +96,6 @@ public List getReleasedVersionsById(String id) { return Collections.emptyList(); } return product.getReleasedVersions(); - } public int updateInitialCount(String productId, int initialCount) { 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..4ff11bf42 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 @@ -62,6 +62,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; import com.axonivy.market.service.ProductService; import com.fasterxml.jackson.core.type.TypeReference; @@ -74,6 +75,7 @@ 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 +97,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; @@ -368,16 +373,23 @@ private void updateProductFromReleaseTags(Product product, GHRepository productR } for (GHTag ghTag : tags) { - ProductModuleContent productModuleContent = - axonIvyProductRepoService.getReadmeAndProductContentsFromTag(product, productRepo, ghTag.getName()); - productModuleContents.add(productModuleContent); + ProductModuleContent productModuleContent = axonIvyProductRepoService.getReadmeAndProductContentsFromTag(product, + productRepo, ghTag.getName()); + if (productModuleContent != null) { + productModuleContent.setProduct(product); + 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)) { + List savedProductModuleContents = productModuleContentRepository + .saveAll(productModuleContents); + product.setProductModuleContents(savedProductModuleContents); + } } private void getPublishedDateFromLatestTag(Product product, GHTag lastTag) { @@ -437,7 +449,6 @@ public Product fetchProductDetail(String id) { }).orElse(null); } - @Override public Product fetchBestMatchProductDetail(String id, String version) { List releasedVersions = productRepository.getReleasedVersionsById(id); From ad352eed6f2b3ad70e1df46210465cf68b125181 Mon Sep 17 00:00:00 2001 From: tutn Date: Thu, 22 Aug 2024 11:56:12 +0700 Subject: [PATCH 02/21] Update fetch product detail --- .../service/impl/ProductServiceImpl.java | 1 - .../product-detail.component.html | 14 ++++----- .../product-detail.component.ts | 30 ++++++++----------- .../app/shared/pipes/has-value-tab.pipe.ts | 20 +++++++++++++ 4 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts 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 4ff11bf42..7db2c64f7 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 @@ -376,7 +376,6 @@ private void updateProductFromReleaseTags(Product product, GHRepository productR ProductModuleContent productModuleContent = axonIvyProductRepoService.getReadmeAndProductContentsFromTag(product, productRepo, ghTag.getName()); if (productModuleContent != null) { - productModuleContent.setProduct(product); productModuleContents.add(productModuleContent); } String versionFromTag = VersionUtils.convertTagToVersion(ghTag.getName()); 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 fe8de3ed1..acf5c200b 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 @@ -85,7 +85,7 @@

aria-labelledby="demo-tab">
aria-labelledby="setup-tab">
= signal(false); isTabDropdownShown: WritableSignal = signal(false); @@ -107,7 +112,6 @@ export class ProductDetailComponent { this.updateDropdownSelection(); } - constructor() { this.scrollToTop(); this.resizeObserver = new ResizeObserver(() => { @@ -169,21 +173,9 @@ export class ProductDetailComponent { }); } - getContent(value: string): boolean { - const content = this.productModuleContent(); - const conditions: { [key: string]: boolean } = { - description: content.description !== null, - demo: content.demo !== null, - setup: content.setup !== null , - dependency: content.isDependency - }; - - return conditions[value] ?? false; - } - loadDetailTabs(selectedVersion: string) { let version = selectedVersion || this.productDetail().newestReleaseVersion; - version = version.replace("Version ","") + version = version.replace('Version ', ''); this.productService .getProductDetailsWithVersion(this.productDetail().id, version) .subscribe(updatedProductDetail => { @@ -278,7 +270,11 @@ export class ProductDetailComponent { } getNotEmptyTabs(): ItemDropdown[] { - return this.detailTabsForDropdown.filter(tab => this.getContent(tab.value)); + return this.detailTabsForDropdown.filter(tab => + HasValueTabPipe.prototype.transform( + tab.value, + this.productModuleContent() + ) + ); } - } diff --git a/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts b/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts new file mode 100644 index 000000000..4108fcc53 --- /dev/null +++ b/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts @@ -0,0 +1,20 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { ProductModuleContent } from "../models/product-module-content.model"; + +@Pipe({ + standalone: true, + name: 'hasValueTab' +}) +export class HasValueTabPipe implements PipeTransform { + transform(value: string, productModuleContent: ProductModuleContent, args?: any): boolean { + console.log('Check') + const conditions: { [key: string]: boolean } = { + description: productModuleContent.description !== null, + demo: productModuleContent.demo !== null, + setup: productModuleContent.setup !== null , + dependency: productModuleContent.isDependency + }; + + return conditions[value] ?? false; + } +} \ No newline at end of file From 6aed3ea0509444abd9be5da4db4e1f1aeb72113e Mon Sep 17 00:00:00 2001 From: tutn Date: Thu, 22 Aug 2024 14:30:46 +0700 Subject: [PATCH 03/21] Update unit test --- .../service/impl/ProductServiceImplTest.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) 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..8a8df68c4 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 = ArgumentCaptor.forClass((Class) List.class); + @Mock private GHAxonIvyProductRepoService ghAxonIvyProductRepoService; @@ -315,14 +324,14 @@ 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() @@ -356,10 +365,13 @@ 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() @@ -547,6 +559,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<>(); From 1d8fc68752ed250caf332a1e1d4428a9e887889a Mon Sep 17 00:00:00 2001 From: tutn Date: Thu, 22 Aug 2024 17:02:18 +0700 Subject: [PATCH 04/21] Update unit test --- .../product-detail.component.spec.ts | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts index 007a5ae21..36b4ae29b 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts @@ -145,52 +145,6 @@ describe('ProductDetailComponent', () => { expect(component.setActiveTab).toHaveBeenCalledWith('description'); }); - it('should return true for description when it is not null and not empty', () => { - const mockContent: ProductModuleContent = { - ...MOCK_PRODUCT_MODULE_CONTENT, - description: { en: 'Test description' } - }; - - component.productModuleContent.set(mockContent); - expect(component.getContent('description')).toBeTrue(); - }); - - it('should return false for description when it is null or empty', () => { - const mockContentWithEmptyDescription: ProductModuleContent = - MOCK_PRODUCT_MODULE_CONTENT; - component.productModuleContent.set(mockContentWithEmptyDescription); - expect(component.getContent('description')).toBeFalse(); - - const mockContentWithNullDescription: ProductModuleContent = { - ...MOCK_PRODUCT_MODULE_CONTENT - }; - component.productModuleContent.set(mockContentWithNullDescription); - expect(component.getContent('description')).toBeFalse(); - }); - - it('should return true for setup when it is not null and not empty', () => { - const mockContent: ProductModuleContent = { - ...MOCK_PRODUCT_MODULE_CONTENT, - setup: { en: 'Test setup' } - }; - - component.productModuleContent.set(mockContent); - expect(component.getContent('setup')).toBeTrue(); - }); - - it('should return false for setup when it is null or empty', () => { - const mockContentWithEmptySetup: ProductModuleContent = - MOCK_PRODUCT_MODULE_CONTENT; - component.productModuleContent.set(mockContentWithEmptySetup); - expect(component.getContent('setup')).toBeFalse(); - - const mockContentWithNullSetup: ProductModuleContent = { - ...MOCK_PRODUCT_MODULE_CONTENT - }; - component.productModuleContent.set(mockContentWithNullSetup); - expect(component.getContent('setup')).toBeFalse(); - }); - it('should display dropdown horizontally on small viewport', () => { viewport.set(540); const tabGroup = fixture.debugElement.query(By.css('.row-tab')); From f7195089419c9c73e1b422d00246ab0eb37c4007 Mon Sep 17 00:00:00 2001 From: tutn Date: Thu, 22 Aug 2024 17:05:57 +0700 Subject: [PATCH 05/21] fix sonar --- marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts b/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts index 4108fcc53..ec6c3d14c 100644 --- a/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts +++ b/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts @@ -6,8 +6,7 @@ import { ProductModuleContent } from "../models/product-module-content.model"; name: 'hasValueTab' }) export class HasValueTabPipe implements PipeTransform { - transform(value: string, productModuleContent: ProductModuleContent, args?: any): boolean { - console.log('Check') + transform(value: string, productModuleContent: ProductModuleContent): boolean { const conditions: { [key: string]: boolean } = { description: productModuleContent.description !== null, demo: productModuleContent.demo !== null, From c54a8d6c00678dd41f2eb52418e5d6aedc7bc3b1 Mon Sep 17 00:00:00 2001 From: tutn Date: Thu, 22 Aug 2024 17:12:22 +0700 Subject: [PATCH 06/21] Fix sonar --- .../java/com/axonivy/market/constants/MongoDBConstants.java | 2 -- .../java/com/axonivy/market/entity/ProductModuleContent.java | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) 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 d85e5554f..98c433de9 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,7 +1,5 @@ package com.axonivy.market.constants; -import java.util.Map; - public class MongoDBConstants { private MongoDBConstants() { 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 ba901fa90..387b510b8 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 @@ -7,14 +7,13 @@ import lombok.NoArgsConstructor; import lombok.Setter; import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.DBRef; 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;; +import static com.axonivy.market.constants.EntityConstants.PRODUCT_MODULE_CONTENT; @Getter @Setter From 6a94c9c46d28aa932b9a204c70fa45aea1679511 Mon Sep 17 00:00:00 2001 From: tutn Date: Fri, 30 Aug 2024 09:50:07 +0700 Subject: [PATCH 07/21] handle feedback --- .../com/axonivy/market/entity/Product.java | 2 +- .../ProductModuleContentRepository.java | 4 +- .../service/impl/ProductServiceImplTest.java | 13 +++--- .../product-detail.component.ts | 44 +++++++++---------- 4 files changed, 33 insertions(+), 30 deletions(-) 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 8567d811c..8c14bfdd3 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 @@ -57,7 +57,7 @@ public class Product implements Serializable { private int installationCount; private Date newestPublishedDate; private String newestReleaseVersion; - @DBRef(lazy = false) + @DBRef private List productModuleContents; private List artifacts; private Boolean synchronizedInstallationCount; 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 index f48ccce38..1f9efb343 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java @@ -6,4 +6,6 @@ import com.axonivy.market.entity.ProductModuleContent; @Repository -public interface ProductModuleContentRepository extends MongoRepository {} +public interface ProductModuleContentRepository extends MongoRepository { + +} 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 8a8df68c4..28a47790d 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 @@ -34,6 +34,7 @@ import java.util.UUID; import com.axonivy.market.criteria.ProductSearchCriteria; +import com.fasterxml.jackson.core.type.TypeReference; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -121,7 +122,7 @@ class ProductServiceImplTest extends BaseSetup { ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Product.class); @Captor - ArgumentCaptor> argumentCaptorProductModuleContent = ArgumentCaptor.forClass((Class) List.class); + ArgumentCaptor> argumentCaptorProductModuleContent; @Mock private GHAxonIvyProductRepoService ghAxonIvyProductRepoService; @@ -149,7 +150,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 @@ -298,8 +299,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())); } @@ -478,8 +479,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()); } diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.ts index 66f06575d..6483a6cfa 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.ts @@ -1,3 +1,4 @@ +import { CommonModule } from '@angular/common'; import { Component, ElementRef, @@ -7,39 +8,38 @@ import { signal } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { ProductService } from '../product.service'; +import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; import { MarkdownModule, MarkdownService } from 'ngx-markdown'; -import { ProductDetail } from '../../../shared/models/product-detail.model'; -import { ProductModuleContent } from '../../../shared/models/product-module-content.model'; +import { Observable } from 'rxjs'; +import { AuthService } from '../../../auth/auth.service'; +import { LanguageService } from '../../../core/services/language/language.service'; import { ThemeService } from '../../../core/services/theme/theme.service'; -import { CommonModule } from '@angular/common'; -import { ProductDetailInformationTabComponent } from './product-detail-information-tab/product-detail-information-tab.component'; -import { ProductDetailVersionActionComponent } from './product-detail-version-action/product-detail-version-action.component'; -import { ProductDetailMavenContentComponent } from './product-detail-maven-content/product-detail-maven-content.component'; +import { CommonDropdownComponent } from '../../../shared/components/common-dropdown/common-dropdown.component'; import { PRODUCT_DETAIL_TABS, VERSION } from '../../../shared/constants/common.constant'; -import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; -import { LanguageService } from '../../../core/services/language/language.service'; +import { ItemDropdown } from '../../../shared/models/item-dropdown.model'; +import { ProductDetail } from '../../../shared/models/product-detail.model'; +import { ProductModuleContent } from '../../../shared/models/product-module-content.model'; +import { HasValueTabPipe } from '../../../shared/pipes/has-value-tab.pipe'; +import { ProductTypeIconPipe } from '../../../shared/pipes/icon.pipe'; import { MultilingualismPipe } from '../../../shared/pipes/multilingualism.pipe'; -import { ProductDetailService } from './product-detail.service'; -import { ProductDetailFeedbackComponent } from './product-detail-feedback/product-detail-feedback.component'; -import { ProductFeedbackService } from './product-detail-feedback/product-feedbacks-panel/product-feedback.service'; +import { ProductTypePipe } from '../../../shared/pipes/product-type.pipe'; import { AppModalService } from '../../../shared/services/app-modal.service'; -import { AuthService } from '../../../auth/auth.service'; -import { ProductStarRatingNumberComponent } from './product-star-rating-number/product-star-rating-number.component'; -import { ProductInstallationCountActionComponent } from './product-installation-count-action/product-installation-count-action.component'; -import { ProductTypeIconPipe } from '../../../shared/pipes/icon.pipe'; -import { Observable } from 'rxjs'; -import { ProductStarRatingService } from './product-detail-feedback/product-star-rating-panel/product-star-rating.service'; import { RoutingQueryParamService } from '../../../shared/services/routing.query.param.service'; -import { CommonDropdownComponent } from '../../../shared/components/common-dropdown/common-dropdown.component'; import { CommonUtils } from '../../../shared/utils/common.utils'; -import { ItemDropdown } from '../../../shared/models/item-dropdown.model'; -import { ProductTypePipe } from '../../../shared/pipes/product-type.pipe'; -import { HasValueTabPipe } from '../../../shared/pipes/has-value-tab.pipe'; +import { ProductService } from '../product.service'; +import { ProductDetailFeedbackComponent } from './product-detail-feedback/product-detail-feedback.component'; +import { ProductFeedbackService } from './product-detail-feedback/product-feedbacks-panel/product-feedback.service'; +import { ProductStarRatingService } from './product-detail-feedback/product-star-rating-panel/product-star-rating.service'; +import { ProductDetailInformationTabComponent } from './product-detail-information-tab/product-detail-information-tab.component'; +import { ProductDetailMavenContentComponent } from './product-detail-maven-content/product-detail-maven-content.component'; +import { ProductDetailVersionActionComponent } from './product-detail-version-action/product-detail-version-action.component'; +import { ProductDetailService } from './product-detail.service'; +import { ProductInstallationCountActionComponent } from './product-installation-count-action/product-installation-count-action.component'; +import { ProductStarRatingNumberComponent } from './product-star-rating-number/product-star-rating-number.component'; export interface DetailTab { activeClass: string; From 0c457056068c130b1b216c89bee615819e0f0099 Mon Sep 17 00:00:00 2001 From: tutn Date: Fri, 30 Aug 2024 10:38:07 +0700 Subject: [PATCH 08/21] Fix sonar --- .../service/impl/ProductServiceImpl.java | 78 ++++++++----------- .../service/impl/ProductServiceImplTest.java | 1 - 2 files changed, 33 insertions(+), 46 deletions(-) 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 7db2c64f7..e875be389 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; @@ -65,10 +22,41 @@ 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 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 28a47790d..768dfea73 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 @@ -34,7 +34,6 @@ import java.util.UUID; import com.axonivy.market.criteria.ProductSearchCriteria; -import com.fasterxml.jackson.core.type.TypeReference; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; From 7075debb45297e704987756b6b7dbc3ed67b51f7 Mon Sep 17 00:00:00 2001 From: tutn Date: Fri, 30 Aug 2024 11:50:52 +0700 Subject: [PATCH 09/21] Update unit test --- .../service/impl/ProductServiceImplTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 768dfea73..faaf06f1b 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 @@ -391,6 +391,26 @@ void testNothingToSync() { assertTrue(result); } + @Test + void testSyncNullProductModuleContent() throws IOException { + var mockCommit = mockGHCommitHasSHA1(SHA1_SAMPLE); + when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit); + when(repoMetaRepository.findByRepoName(anyString())).thenReturn(null); + + GHTag mockTag = mock(GHTag.class); + GHCommit mockGHCommit = mock(GHCommit.class); + + 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().getProductModuleContents()).isNull(); + } + @Test void testSearchProducts() { var simplePageable = PageRequest.of(0, 20); From 8c39db886e947c7613404f678ee6a1609269bdf8 Mon Sep 17 00:00:00 2001 From: tutn Date: Fri, 30 Aug 2024 15:23:55 +0700 Subject: [PATCH 10/21] change to use builder --- .../impl/CustomProductRepositoryImpl.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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 aa7114bcc..4ef866bcf 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 @@ -40,14 +40,12 @@ public Document createDocumentFilterProductModuleContentByTag(String tag) { return loopOverProductModuleContents.append(MongoDBConstants.CONDITION, isProductModuleContentOfCurrentTag); } - - private AggregationOperation createLookupProductModuleContentOperation() { - return context -> new Document(MongoDBConstants.LOOKUP, - new Document() - .append(MongoDBConstants.FROM, MongoDBConstants.PRODUCT_MODULE_CONTENT_DOCUMENT) - .append(MongoDBConstants.LOCAL_FIELD, MongoDBConstants.PRODUCT_MODULE_CONTENTS_KEY_FIELD) - .append(MongoDBConstants.FOREIGN_FIELD, MongoDBConstants.ID) - .append(MongoDBConstants.AS, MongoDBConstants.PRODUCT_MODULE_CONTENTS)); + public AggregationOperation createLookupProductModuleContentOperation() { + return Aggregation.lookup() + .from(MongoDBConstants.PRODUCT_MODULE_CONTENT_DOCUMENT) + .localField(MongoDBConstants.PRODUCT_MODULE_CONTENTS_KEY_FIELD) + .foreignField(MongoDBConstants.ID) + .as(MongoDBConstants.PRODUCT_MODULE_CONTENTS); } private AggregationOperation createAddFieldProductModuleContentOperation() { From 7d0039c8158ab992b4467cd47a20fed60869d004 Mon Sep 17 00:00:00 2001 From: tutn Date: Fri, 30 Aug 2024 15:27:33 +0700 Subject: [PATCH 11/21] fix npe --- marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts b/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts index ec6c3d14c..7baff42d6 100644 --- a/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts +++ b/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts @@ -8,10 +8,10 @@ import { ProductModuleContent } from "../models/product-module-content.model"; export class HasValueTabPipe implements PipeTransform { transform(value: string, productModuleContent: ProductModuleContent): boolean { const conditions: { [key: string]: boolean } = { - description: productModuleContent.description !== null, - demo: productModuleContent.demo !== null, - setup: productModuleContent.setup !== null , - dependency: productModuleContent.isDependency + description: productModuleContent && productModuleContent.description !== null, + demo: productModuleContent && productModuleContent.demo !== null, + setup: productModuleContent && productModuleContent.setup !== null , + dependency: productModuleContent && productModuleContent.isDependency }; return conditions[value] ?? false; From 44749153122341907d320d040ad65ff8420b6906 Mon Sep 17 00:00:00 2001 From: tutn Date: Fri, 30 Aug 2024 15:56:27 +0700 Subject: [PATCH 12/21] remove unused unit test --- .../product-detail.component.spec.ts | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts index 5a6199ffa..031072a7b 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts @@ -149,61 +149,6 @@ describe('ProductDetailComponent', () => { expect(component.setActiveTab).toHaveBeenCalledWith('description'); }); - it('should return true for description when it is not null and not empty', () => { - const mockContent: ProductModuleContent = { - ...MOCK_PRODUCT_MODULE_CONTENT, - description: { en: 'Test description' } - }; - - component.productModuleContent.set(mockContent); - expect(component.getContent('description')).toBeTrue(); - }); - - it('should return false in tab visibility when product module content is missing', () => { - const mockEmptyContent: ProductModuleContent = {} as ProductModuleContent; - component.productModuleContent.set(mockEmptyContent); - expect(component.getContent('description')).toBeFalse(); - expect(component.getContent('demo')).toBeFalse(); - expect(component.getContent('setup')).toBeFalse(); - expect(component.getContent('dependency')).toBeFalse(); - }); - - it('should return false for description when it is null or empty', () => { - const mockContentWithEmptyDescription: ProductModuleContent = - MOCK_PRODUCT_MODULE_CONTENT; - component.productModuleContent.set(mockContentWithEmptyDescription); - expect(component.getContent('description')).toBeFalse(); - - const mockContentWithNullDescription: ProductModuleContent = { - ...MOCK_PRODUCT_MODULE_CONTENT - }; - component.productModuleContent.set(mockContentWithNullDescription); - expect(component.getContent('description')).toBeFalse(); - }); - - it('should return true for setup when it is not null and not empty', () => { - const mockContent: ProductModuleContent = { - ...MOCK_PRODUCT_MODULE_CONTENT, - setup: { en: 'Test setup' } - }; - - component.productModuleContent.set(mockContent); - expect(component.getContent('setup')).toBeTrue(); - }); - - it('should return false for setup when it is null or empty', () => { - const mockContentWithEmptySetup: ProductModuleContent = - MOCK_PRODUCT_MODULE_CONTENT; - component.productModuleContent.set(mockContentWithEmptySetup); - expect(component.getContent('setup')).toBeFalse(); - - const mockContentWithNullSetup: ProductModuleContent = { - ...MOCK_PRODUCT_MODULE_CONTENT - }; - component.productModuleContent.set(mockContentWithNullSetup); - expect(component.getContent('setup')).toBeFalse(); - }); - it('should not display information when product detail is empty', () => { const mockContentWithEmptySetup: ProductModuleContent = {} as ProductModuleContent; From 6861e9c2f15d920dfb45354799e04b4e2581d739 Mon Sep 17 00:00:00 2001 From: tutn Date: Fri, 30 Aug 2024 16:05:40 +0700 Subject: [PATCH 13/21] fix sonar --- marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts b/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts index 7baff42d6..e9be1a086 100644 --- a/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts +++ b/marketplace-ui/src/app/shared/pipes/has-value-tab.pipe.ts @@ -8,10 +8,10 @@ import { ProductModuleContent } from "../models/product-module-content.model"; export class HasValueTabPipe implements PipeTransform { transform(value: string, productModuleContent: ProductModuleContent): boolean { const conditions: { [key: string]: boolean } = { - description: productModuleContent && productModuleContent.description !== null, - demo: productModuleContent && productModuleContent.demo !== null, - setup: productModuleContent && productModuleContent.setup !== null , - dependency: productModuleContent && productModuleContent.isDependency + description: productModuleContent?.description !== null, + demo: productModuleContent?.demo !== null, + setup: productModuleContent?.setup !== null , + dependency: productModuleContent?.isDependency }; return conditions[value] ?? false; From dbe430f85680acabd3f5e8b4feba51e84d9189b2 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Thu, 5 Sep 2024 11:38:37 +0700 Subject: [PATCH 14/21] handle feedback --- .../axonivy/market/service/impl/ProductServiceImpl.java | 4 ++-- .../market/service/impl/ProductServiceImplTest.java | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) 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 e875be389..4d4a2f842 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 @@ -361,8 +361,8 @@ private void updateProductFromReleaseTags(Product product, GHRepository productR } for (GHTag ghTag : tags) { - ProductModuleContent productModuleContent = axonIvyProductRepoService.getReadmeAndProductContentsFromTag(product, - productRepo, ghTag.getName()); + ProductModuleContent productModuleContent = + axonIvyProductRepoService.getReadmeAndProductContentsFromTag(product, productRepo, ghTag.getName()); if (productModuleContent != null) { productModuleContents.add(productModuleContent); } 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 faaf06f1b..a04e0894f 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 @@ -392,14 +392,11 @@ void testNothingToSync() { } @Test - void testSyncNullProductModuleContent() throws IOException { + void testSyncNullProductModuleContent() { var mockCommit = mockGHCommitHasSHA1(SHA1_SAMPLE); when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit); when(repoMetaRepository.findByRepoName(anyString())).thenReturn(null); - - GHTag mockTag = mock(GHTag.class); - GHCommit mockGHCommit = mock(GHCommit.class); - + Map> mockGHContentMap = new HashMap<>(); mockGHContentMap.put(SAMPLE_PRODUCT_ID, new ArrayList<>()); when(marketRepoService.fetchAllMarketItems()).thenReturn(mockGHContentMap); From d5ef1109ee52ded029a0f3d96fe5ed456d51f9fe Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Thu, 5 Sep 2024 17:36:03 +0700 Subject: [PATCH 15/21] update service --- .../main/java/com/axonivy/market/entity/Product.java | 2 -- .../axonivy/market/entity/ProductModuleContent.java | 2 ++ .../repository/ProductModuleContentRepository.java | 8 ++++++-- .../market/service/impl/ProductServiceImpl.java | 11 ++++++----- .../java/com/axonivy/market/util/VersionUtils.java | 10 ++++++++++ 5 files changed, 24 insertions(+), 9 deletions(-) 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 1c8cef22d..fd6cb0b7e 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 @@ -58,8 +58,6 @@ public class Product implements Serializable { private int installationCount; private Date newestPublishedDate; private String newestReleaseVersion; - @DBRef - private List productModuleContents; private List artifacts; private Boolean synchronizedInstallationCount; private Integer customOrder; 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 387b510b8..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 @@ -26,6 +26,8 @@ public class ProductModuleContent implements Serializable { 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/repository/ProductModuleContentRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java index 1f9efb343..763ad4a39 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java @@ -1,11 +1,15 @@ package com.axonivy.market.repository; +import com.axonivy.market.entity.Feedback; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; -import com.axonivy.market.entity.ProductModuleContent; +import java.util.List; @Repository -public interface ProductModuleContentRepository extends MongoRepository { +public interface ProductModuleContentRepository extends MongoRepository { } 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 4d4a2f842..e455f6dac 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 @@ -343,24 +343,25 @@ 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(); + 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()); if (productModuleContent != null) { 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..be8fee5c1 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,11 @@ 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.getVersion().map(VersionUtils::convertVersionToTag).orElse(); + } + } From c82f561c82c2179b7ce8fe1fdffce2c31d662ae9 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 6 Sep 2024 01:06:07 +0700 Subject: [PATCH 16/21] update service --- .../ProductDetailModelAssembler.java | 2 +- .../market/constants/MongoDBConstants.java | 16 +---- .../controller/ProductDetailsController.java | 2 +- .../com/axonivy/market/entity/Product.java | 3 +- .../impl/GHAxonIvyProductRepoServiceImpl.java | 1 + .../ProductModuleContentRepository.java | 5 +- .../impl/CustomProductRepositoryImpl.java | 65 +++++-------------- .../service/impl/ProductServiceImpl.java | 7 +- .../com/axonivy/market/util/VersionUtils.java | 3 +- 9 files changed, 31 insertions(+), 73 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 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/MongoDBConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/MongoDBConstants.java index 98c433de9..969a9d02c 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 @@ -6,25 +6,15 @@ 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_CONTENTS_KEY_FIELD ="productModuleContents.$id"; 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_MODULE_CONTENT_FILTER_TAG ="productModuleContent.tag"; public static final String PRODUCT_COLLECTION ="Product"; - public static final String NEWEST_RELEASED_VERSION_QUERY = "$newestReleaseVersion"; - public static final String PRODUCT_MODULE_CONTENT_DOCUMENT ="ProductModuleContent"; - public static final String UNWIND = "$unwind"; - public static final String LOOKUP = "$lookup"; - public static final String MATCH = "$match"; - public static final String FROM = "from"; - public static final String LOCAL_FIELD = "localField"; - public static final String FOREIGN_FIELD = "foreignField"; + public static final String INSTALLATION_COUNT ="Product"; + 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 fd6cb0b7e..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 @@ -58,11 +58,12 @@ public class Product implements Serializable { private int installationCount; private Date newestPublishedDate; private String newestReleaseVersion; + @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/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 index 763ad4a39..e789ce352 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java @@ -1,6 +1,7 @@ package com.axonivy.market.repository; import com.axonivy.market.entity.Feedback; +import com.axonivy.market.entity.ProductModuleContent; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; @@ -10,6 +11,6 @@ import java.util.List; @Repository -public interface ProductModuleContentRepository extends MongoRepository { - +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 4ef866bcf..cf7d09d53 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,18 +2,17 @@ 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 org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.FindAndModifyOptions; import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.aggregation.Aggregation; -import org.springframework.data.mongodb.core.aggregation.AggregationOperation; -import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.aggregation.*; import org.springframework.data.mongodb.core.query.Criteria; 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; @@ -22,45 +21,18 @@ public class CustomProductRepositoryImpl implements CustomProductRepository { private final MongoTemplate mongoTemplate; + @Autowired + 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); - } - - public AggregationOperation createLookupProductModuleContentOperation() { - return Aggregation.lookup() - .from(MongoDBConstants.PRODUCT_MODULE_CONTENT_DOCUMENT) - .localField(MongoDBConstants.PRODUCT_MODULE_CONTENTS_KEY_FIELD) - .foreignField(MongoDBConstants.ID) - .as(MongoDBConstants.PRODUCT_MODULE_CONTENTS); - } - - private AggregationOperation createAddFieldProductModuleContentOperation() { - 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); @@ -68,20 +40,18 @@ public Product queryProductByAggregation(Aggregation aggregation) { @Override public Product getProductByIdAndTag(String id, String tag) { - // Create the aggregation pipeline - Aggregation aggregation = Aggregation.newAggregation( - createIdMatchOperation(id), - createLookupProductModuleContentOperation(), - createReturnFirstMatchTagModuleContentOperation(tag)); - return queryProductByAggregation(aggregation); + Product result = getProductById(id); + if (!Objects.isNull(result)) { + ProductModuleContent content = contentRepository.findByTagAndProductId(tag,id); + result.setProductModuleContent(content); + } + return result; } @Override public Product getProductById(String id) { Aggregation aggregation = Aggregation.newAggregation( - createIdMatchOperation(id), - createLookupProductModuleContentOperation(), - createAddFieldProductModuleContentOperation() + createIdMatchOperation(id) ); return queryProductByAggregation(aggregation); } @@ -97,15 +67,14 @@ public List getReleasedVersionsById(String id) { } 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 e455f6dac..2a7f6a3e8 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 @@ -354,10 +354,8 @@ private void updateProductFromReleaseTags(Product product, GHRepository productR 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(); + if (!CollectionUtils.isEmpty(product.getReleasedVersions())) { + List currentTags = VersionUtils.getReleaseTagsFromProduct(product); ghTags = ghTags.stream().filter(t -> !currentTags.contains(t.getName())).toList(); } @@ -376,7 +374,6 @@ private void updateProductFromReleaseTags(Product product, GHRepository productR if (!CollectionUtils.isEmpty(productModuleContents)) { List savedProductModuleContents = productModuleContentRepository .saveAll(productModuleContents); - product.setProductModuleContents(savedProductModuleContents); } } 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 be8fee5c1..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 @@ -116,7 +116,6 @@ public static List getReleaseTagsFromProduct(Product product) { if (Objects.isNull(product)) { return new ArrayList<>(); } - return product.getVersion().map(VersionUtils::convertVersionToTag).orElse(); + return product.getReleasedVersions().stream().map(version -> convertVersionToTag(product.getId(), version)).toList(); } - } From 50245b5be099da94eedeb7dcfc5c03289cb2dd00 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 6 Sep 2024 01:36:30 +0700 Subject: [PATCH 17/21] update test case --- .../impl/CustomProductRepositoryImpl.java | 3 +- .../impl/CustomProductRepositoryImplTest.java | 28 ++++--------------- .../service/impl/ProductServiceImplTest.java | 11 ++++---- 3 files changed, 12 insertions(+), 30 deletions(-) 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 cf7d09d53..54b73b22f 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 @@ -5,6 +5,7 @@ import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.repository.CustomProductRepository; import com.axonivy.market.repository.ProductModuleContentRepository; +import lombok.Builder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.FindAndModifyOptions; import org.springframework.data.mongodb.core.MongoTemplate; @@ -18,7 +19,7 @@ import java.util.Objects; import java.util.Optional; - +@Builder public class CustomProductRepositoryImpl implements CustomProductRepository { private final MongoTemplate mongoTemplate; @Autowired 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..744fd7762 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(); @@ -134,12 +124,4 @@ void testIncreaseInstallationCount_NullProduct() { int updatedCount = repo.increaseInstallationCount(ID); assertEquals(0, updatedCount); } - - @Test - void testUpdateInitialCount() { - setUpMockAggregateResult(); - int initialCount = 10; - repo.updateInitialCount(ID, initialCount); - verify(mongoTemplate).updateFirst(any(Query.class), eq(new Update().inc("InstallationCount", initialCount).set("SynchronizedInstallationCount", true)), eq(Product.class)); - } } 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 a04e0894f..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 @@ -334,7 +334,7 @@ void testSyncProductsFirstTime() throws IOException { verify(productModuleContentRepository).saveAll(argumentCaptorProductModuleContent.capture()); verify(productRepository).save(argumentCaptor.capture()); - assertThat(argumentCaptor.getValue().getProductModuleContents()).usingRecursiveComparison() + assertThat(argumentCaptorProductModuleContent.getValue()).usingRecursiveComparison() .isEqualTo(List.of(mockReadmeProductContent())); } @@ -373,9 +373,8 @@ void testSyncProductsSecondTime() throws IOException { 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 @@ -405,7 +404,7 @@ void testSyncNullProductModuleContent() { productService.syncLatestDataFromMarketRepo(); verify(productRepository).save(argumentCaptor.capture()); - assertThat(argumentCaptor.getValue().getProductModuleContents()).isNull(); + assertThat(argumentCaptor.getValue().getProductModuleContent()).isNull(); } @Test @@ -587,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); } } From 73cab2dd3f63e345eed0e0fce20f40a04368122d Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 6 Sep 2024 09:49:50 +0700 Subject: [PATCH 18/21] Update CustomProductRepositoryImpl.java --- .../impl/CustomProductRepositoryImpl.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) 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 54b73b22f..2a9607334 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 @@ -5,6 +5,7 @@ import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.repository.CustomProductRepository; import com.axonivy.market.repository.ProductModuleContentRepository; +import com.axonivy.market.util.VersionUtils; import lombok.Builder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.FindAndModifyOptions; @@ -41,7 +42,7 @@ public Product queryProductByAggregation(Aggregation aggregation) { @Override public Product getProductByIdAndTag(String id, String tag) { - Product result = getProductById(id); + Product result = findProductById(id); if (!Objects.isNull(result)) { ProductModuleContent content = contentRepository.findByTagAndProductId(tag,id); result.setProductModuleContent(content); @@ -49,12 +50,20 @@ public Product getProductByIdAndTag(String id, String tag) { 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) - ); - return queryProductByAggregation(aggregation); + Product result = findProductById(id); + if (!Objects.isNull(result)) { + ProductModuleContent content = contentRepository.findByTagAndProductId( + result.getNewestReleaseVersion(), id); + result.setProductModuleContent(content); + } + return result; } @Override From 67c1a217dabb590740b34c4d145453d0ea1158ec Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 6 Sep 2024 10:00:11 +0700 Subject: [PATCH 19/21] handle sonnar --- .../market/repository/ProductModuleContentRepository.java | 6 ------ .../market/repository/impl/CustomProductRepositoryImpl.java | 2 -- .../com/axonivy/market/service/impl/ProductServiceImpl.java | 3 +-- 3 files changed, 1 insertion(+), 10 deletions(-) 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 index e789ce352..75bdc53db 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductModuleContentRepository.java @@ -1,15 +1,9 @@ package com.axonivy.market.repository; -import com.axonivy.market.entity.Feedback; import com.axonivy.market.entity.ProductModuleContent; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; -import java.util.List; - @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 2a9607334..2d4815fb1 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 @@ -5,7 +5,6 @@ import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.repository.CustomProductRepository; import com.axonivy.market.repository.ProductModuleContentRepository; -import com.axonivy.market.util.VersionUtils; import lombok.Builder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.FindAndModifyOptions; @@ -23,7 +22,6 @@ @Builder public class CustomProductRepositoryImpl implements CustomProductRepository { private final MongoTemplate mongoTemplate; - @Autowired private final ProductModuleContentRepository contentRepository; public CustomProductRepositoryImpl(MongoTemplate mongoTemplate, ProductModuleContentRepository contentRepository) { 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 2a7f6a3e8..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 @@ -372,8 +372,7 @@ private void updateProductFromReleaseTags(Product product, GHRepository productR product.getReleasedVersions().add(versionFromTag); } if (!CollectionUtils.isEmpty(productModuleContents)) { - List savedProductModuleContents = productModuleContentRepository - .saveAll(productModuleContents); + productModuleContentRepository.saveAll(productModuleContents); } } From 93dc8b834ac38b60df691407ec91f88d78c49e83 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 6 Sep 2024 10:18:45 +0700 Subject: [PATCH 20/21] Update MongoDBConstants.java --- .../java/com/axonivy/market/constants/MongoDBConstants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 969a9d02c..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 @@ -14,7 +14,7 @@ private MongoDBConstants() { 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 INSTALLATION_COUNT ="Product"; + public static final String INSTALLATION_COUNT = "InstallationCount"; public static final String SYNCHRONIZED_INSTALLATION_COUNT ="SynchronizedInstallationCount"; } From ecd4814f2a9ceccd7fa8c9264da9a6b4fff4bdb4 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 6 Sep 2024 10:24:39 +0700 Subject: [PATCH 21/21] handle sonnar --- .../repository/impl/CustomProductRepositoryImpl.java | 5 +++-- .../repository/impl/CustomProductRepositoryImplTest.java | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) 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 2d4815fb1..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 @@ -6,10 +6,11 @@ import com.axonivy.market.repository.CustomProductRepository; import com.axonivy.market.repository.ProductModuleContentRepository; import lombok.Builder; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.FindAndModifyOptions; import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.aggregation.*; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; 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 744fd7762..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 @@ -124,4 +124,12 @@ void testIncreaseInstallationCount_NullProduct() { int updatedCount = repo.increaseInstallationCount(ID); assertEquals(0, updatedCount); } + + @Test + void testUpdateInitialCount() { + setUpMockAggregateResult(); + int initialCount = 10; + repo.updateInitialCount(ID, initialCount); + verify(mongoTemplate).updateFirst(any(Query.class), eq(new Update().inc("InstallationCount", initialCount).set("SynchronizedInstallationCount", true)), eq(Product.class)); + } }