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 fc813d23a..157aa49d4 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 @@ -7,6 +7,7 @@ public class EntityConstants { public static final String USER = "User"; public static final String PRODUCT = "Product"; + public static final String PRODUCT_DESIGNER_INSTALLATION = "ProductDesignerInstallation"; public static final String MAVEN_ARTIFACT_VERSION = "MavenArtifactVersion"; public static final String GH_REPO_META = "GitHubRepoMeta"; public static final String FEEDBACK = "Feedback"; 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 df9c3c1e0..6a41ad547 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 @@ -16,5 +16,6 @@ private MongoDBConstants() { public static final String PRODUCT_COLLECTION ="Product"; public static final String INSTALLATION_COUNT = "InstallationCount"; public static final String SYNCHRONIZED_INSTALLATION_COUNT ="SynchronizedInstallationCount"; - + public static final String PRODUCT_ID = "productId"; + public static final String DESIGNER_VERSION = "designerVersion"; } diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java index 6890a6424..abfc30ba3 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java @@ -10,6 +10,7 @@ public class RequestMappingConstants { public static final String SYNC = "sync"; public static final String PRODUCT = API + "/product"; public static final String PRODUCT_DETAILS = API + "/product-details"; + public static final String PRODUCT_DESIGNER_INSTALLATION = API + "/product-designer-installation"; public static final String FEEDBACK = API + "/feedback"; public static final String SWAGGER_URL = "/swagger-ui/index.html"; public static final String GIT_HUB_LOGIN = "/github/login"; @@ -23,5 +24,7 @@ public class RequestMappingConstants { public static final String INSTALLATION_COUNT_BY_ID = "/installationcount/{id}"; public static final String PRODUCT_JSON_CONTENT_BY_PRODUCT_ID_AND_VERSION = "/productjsoncontent/{productId}/{version}"; public static final String VERSIONS_IN_DESIGNER = "/{id}/designerversions"; + public static final String DESIGNER_INSTALLATION_BY_PRODUCT_ID_AND_DESIGNER_VERSION = "/installation/{productId}/designer/{designerVersion}"; + public static final String DESIGNER_INSTALLATION_BY_PRODUCT_ID = "/installation/{productId}/designer"; public static final String CUSTOM_SORT = "custom-sort"; } \ No newline at end of file diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDesignerInstallationController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDesignerInstallationController.java new file mode 100644 index 000000000..645717df2 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDesignerInstallationController.java @@ -0,0 +1,38 @@ +package com.axonivy.market.controller; + +import com.axonivy.market.model.DesignerInstallation; +import com.axonivy.market.service.ProductDesignerInstallationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static com.axonivy.market.constants.RequestMappingConstants.DESIGNER_INSTALLATION_BY_PRODUCT_ID; +import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_DESIGNER_INSTALLATION; +import static com.axonivy.market.constants.RequestParamConstants.PRODUCT_ID; + +@RestController +@RequestMapping(PRODUCT_DESIGNER_INSTALLATION) +@Tag(name = "Product Designer Installation Controllers", description = "API collection to get designer installation count.") +public class ProductDesignerInstallationController { + private final ProductDesignerInstallationService productDesignerInstallationService; + + public ProductDesignerInstallationController(ProductDesignerInstallationService productDesignerInstallationService) { + this.productDesignerInstallationService = productDesignerInstallationService; + } + + @GetMapping(DESIGNER_INSTALLATION_BY_PRODUCT_ID) + @Operation(summary = "Get designer installation count by product id.", description = "get designer installation count by product id") + public ResponseEntity> getProductDesignerInstallationByProductId(@PathVariable(PRODUCT_ID) @Parameter(description = "Product id (from meta.json)", example = "adobe-acrobat-connector", in = ParameterIn.PATH) String productId) { + List models = productDesignerInstallationService.findByProductId(productId); + return new ResponseEntity<>(models, HttpStatus.OK); + } +} 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 7c4efc906..bdb4f4e84 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 @@ -1,18 +1,18 @@ package com.axonivy.market.controller; -import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_JSON_CONTENT_BY_PRODUCT_ID_AND_VERSION; -import static com.axonivy.market.constants.RequestMappingConstants.VERSIONS_IN_DESIGNER; import static com.axonivy.market.constants.RequestParamConstants.DESIGNER_VERSION; import static com.axonivy.market.constants.RequestParamConstants.ID; import static com.axonivy.market.constants.RequestParamConstants.PRODUCT_ID; import static com.axonivy.market.constants.RequestParamConstants.SHOW_DEV_VERSION; import static com.axonivy.market.constants.RequestParamConstants.VERSION; +import static com.axonivy.market.constants.RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION; import static com.axonivy.market.constants.RequestMappingConstants.BY_ID; import static com.axonivy.market.constants.RequestMappingConstants.BY_ID_AND_VERSION; import static com.axonivy.market.constants.RequestMappingConstants.INSTALLATION_COUNT_BY_ID; import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_DETAILS; +import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_JSON_CONTENT_BY_PRODUCT_ID_AND_VERSION; import static com.axonivy.market.constants.RequestMappingConstants.VERSIONS_BY_ID; -import static com.axonivy.market.constants.RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION; +import static com.axonivy.market.constants.RequestMappingConstants.VERSIONS_IN_DESIGNER; import java.util.List; import java.util.Map; @@ -46,7 +46,8 @@ public class ProductDetailsController { private final ProductService productService; private final ProductDetailModelAssembler detailModelAssembler; - public ProductDetailsController(VersionService versionService, ProductService productService, ProductDetailModelAssembler detailModelAssembler) { + public ProductDetailsController(VersionService versionService, ProductService productService, + ProductDetailModelAssembler detailModelAssembler) { this.versionService = versionService; this.productService = productService; this.detailModelAssembler = detailModelAssembler; @@ -74,8 +75,9 @@ public ResponseEntity findBestMatchProductDetailsByVersion( @PutMapping(INSTALLATION_COUNT_BY_ID) @Operation(summary = "Update installation count of product", description = "By default, increase installation count when click download product files by users") public ResponseEntity syncInstallationCount( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String productId) { - int result = productService.updateInstallationCountForProduct(productId); + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String productId, + @RequestParam(name = DESIGNER_VERSION, required = false) @Parameter(in = ParameterIn.QUERY, example = "v10.0.20") String designerVersion) { + int result = productService.updateInstallationCountForProduct(productId, designerVersion); return new ResponseEntity<>(result, HttpStatus.OK); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/ProductDesignerInstallation.java b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductDesignerInstallation.java new file mode 100644 index 000000000..7c913c67d --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductDesignerInstallation.java @@ -0,0 +1,42 @@ +package com.axonivy.market.entity; + +import lombok.*; +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.Document; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +import static com.axonivy.market.constants.EntityConstants.PRODUCT_DESIGNER_INSTALLATION; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Document(PRODUCT_DESIGNER_INSTALLATION) +public class ProductDesignerInstallation implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + @Id + private String id; + private String productId; + private String designerVersion; + private int installationCount; + + @Override + public int hashCode() { + return new HashCodeBuilder().append(productId).hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + return new EqualsBuilder().append(productId, ((ProductDesignerInstallation) obj).getProductId()).isEquals(); + } +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/model/DesignerInstallation.java b/marketplace-service/src/main/java/com/axonivy/market/model/DesignerInstallation.java new file mode 100644 index 000000000..398ad26af --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/model/DesignerInstallation.java @@ -0,0 +1,17 @@ +package com.axonivy.market.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class DesignerInstallation { + @Schema(description = "Ivy designer version", example = "11.4.0") + private String designerVersion; + private int numberOfDownloads; +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductRepository.java index 3cd980656..3fc59e926 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductRepository.java @@ -14,4 +14,6 @@ public interface CustomProductRepository { int updateInitialCount(String productId, int initialCount); int increaseInstallationCount(String productId); + + void increaseInstallationCountForProductByDesignerVersion(String productId, String designerVersion); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductDesignerInstallationRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductDesignerInstallationRepository.java new file mode 100644 index 000000000..2b5789521 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductDesignerInstallationRepository.java @@ -0,0 +1,14 @@ +package com.axonivy.market.repository; + +import com.axonivy.market.entity.ProductDesignerInstallation; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ProductDesignerInstallationRepository extends MongoRepository { + + List findByProductId(String productId, Sort sort); +} 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 34a538b15..efb1ef4be 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 @@ -3,6 +3,7 @@ import com.axonivy.market.constants.MongoDBConstants; import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductModuleContent; +import com.axonivy.market.entity.ProductDesignerInstallation; import com.axonivy.market.repository.CustomProductRepository; import com.axonivy.market.repository.ProductModuleContentRepository; import lombok.Builder; @@ -92,4 +93,16 @@ public int increaseInstallationCount(String productId) { private Query createQueryById(String id) { return new Query(Criteria.where(MongoDBConstants.ID).is(id)); } + + @Override + public void increaseInstallationCountForProductByDesignerVersion(String productId, String designerVersion) { + Update update = new Update().inc(MongoDBConstants.INSTALLATION_COUNT, 1); + mongoTemplate.upsert(createQueryByProductIdAndDesignerVersion(productId, designerVersion), + update, ProductDesignerInstallation.class); + } + + private Query createQueryByProductIdAndDesignerVersion(String productId, String designerVersion) { + return new Query(Criteria.where(MongoDBConstants.PRODUCT_ID).is(productId) + .andOperator(Criteria.where(MongoDBConstants.DESIGNER_VERSION).is(designerVersion))); + } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImpl.java b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImpl.java index 538f8f447..68a86856f 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImpl.java @@ -34,7 +34,6 @@ public ProductSearchRepositoryImpl(MongoTemplate mongoTemplate) { this.mongoTemplate = mongoTemplate; } - @Override public Page searchByCriteria(ProductSearchCriteria searchCriteria, Pageable pageable) { return getResultAsPageable(pageable, buildCriteriaSearch(searchCriteria)); diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/ProductDesignerInstallationService.java b/marketplace-service/src/main/java/com/axonivy/market/service/ProductDesignerInstallationService.java new file mode 100644 index 000000000..046a7be7f --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/service/ProductDesignerInstallationService.java @@ -0,0 +1,9 @@ +package com.axonivy.market.service; + +import com.axonivy.market.model.DesignerInstallation; + +import java.util.List; + +public interface ProductDesignerInstallationService { + List findByProductId(String productId); +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java b/marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java index f63495616..44c27771f 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java @@ -11,7 +11,7 @@ public interface ProductService { boolean syncLatestDataFromMarketRepo(); - int updateInstallationCountForProduct(String key); + int updateInstallationCountForProduct(String key, String designerVersion); Product fetchProductDetail(String id); diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImpl.java new file mode 100644 index 000000000..c95241e08 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImpl.java @@ -0,0 +1,35 @@ +package com.axonivy.market.service.impl; + +import com.axonivy.market.constants.MongoDBConstants; +import com.axonivy.market.entity.ProductDesignerInstallation; +import com.axonivy.market.model.DesignerInstallation; +import com.axonivy.market.repository.ProductDesignerInstallationRepository; +import com.axonivy.market.service.ProductDesignerInstallationService; +import lombok.extern.log4j.Log4j2; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Log4j2 +@Service +public class ProductDesignerInstallationServiceImpl implements ProductDesignerInstallationService { + private final ProductDesignerInstallationRepository productDesignerInstallationRepository; + + public ProductDesignerInstallationServiceImpl(ProductDesignerInstallationRepository productDesignerInstallationRepository) { + this.productDesignerInstallationRepository = productDesignerInstallationRepository; + } + + @Override + public List findByProductId(String productId) { + List designerInstallations = new ArrayList<>(); + List productDesignerInstallations = + productDesignerInstallationRepository.findByProductId(productId, Sort.by(Sort.Direction.DESC, MongoDBConstants.DESIGNER_VERSION)); + for (ProductDesignerInstallation productDesignerInstallation : productDesignerInstallations) { + DesignerInstallation designerInstallation = new DesignerInstallation(productDesignerInstallation.getDesignerVersion(), productDesignerInstallation.getInstallationCount()); + designerInstallations.add(designerInstallation); + } + return designerInstallations; + } +} 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 dbeea37d2..a5c2f3e6b 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 @@ -144,11 +144,17 @@ public boolean syncLatestDataFromMarketRepo() { } @Override - public int updateInstallationCountForProduct(String key) { + public int updateInstallationCountForProduct(String key, String designerVersion) { Product product= productRepository.getProductById(key); if (Objects.isNull(product)){ return 0; } + + log.info("Increase installation count for product {} By Designer Version {}", key, designerVersion); + if (StringUtils.isNotBlank(designerVersion)) { + productRepository.increaseInstallationCountForProductByDesignerVersion(key, designerVersion); + } + log.info("updating installation count for product {}", key); if (BooleanUtils.isTrue(product.getSynchronizedInstallationCount())) { return productRepository.increaseInstallationCount(key); diff --git a/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java b/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java index d53fddf43..35ca031e0 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java +++ b/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java @@ -2,8 +2,10 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import com.axonivy.market.entity.ProductDesignerInstallation; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; @@ -41,4 +43,20 @@ protected Page createPageProductsMock() { mockProducts.add(mockProduct); return new PageImpl<>(mockProducts); } + + protected List createProductDesignerInstallationsMock() { + var mockProductDesignerInstallations = new ArrayList(); + ProductDesignerInstallation mockProductDesignerInstallation = new ProductDesignerInstallation(); + mockProductDesignerInstallation.setProductId(SAMPLE_PRODUCT_ID); + mockProductDesignerInstallation.setDesignerVersion("10.0.22"); + mockProductDesignerInstallation.setInstallationCount(50); + mockProductDesignerInstallations.add(mockProductDesignerInstallation); + + mockProductDesignerInstallation = new ProductDesignerInstallation(); + mockProductDesignerInstallation.setProductId(SAMPLE_PRODUCT_ID); + mockProductDesignerInstallation.setDesignerVersion("11.4.0"); + mockProductDesignerInstallation.setInstallationCount(30); + mockProductDesignerInstallations.add(mockProductDesignerInstallation); + return mockProductDesignerInstallations; + } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDesignerInstallationControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDesignerInstallationControllerTest.java new file mode 100644 index 000000000..b8fd3f553 --- /dev/null +++ b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDesignerInstallationControllerTest.java @@ -0,0 +1,39 @@ +package com.axonivy.market.controller; + +import com.axonivy.market.model.DesignerInstallation; +import com.axonivy.market.service.ProductDesignerInstallationService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.List; +import java.util.Objects; + +@ExtendWith(MockitoExtension.class) +class ProductDesignerInstallationControllerTest { + public static final String DESIGNER_VERSION = "11.4.0"; + + @Mock + ProductDesignerInstallationService productDesignerInstallationService; + + @InjectMocks + private ProductDesignerInstallationController productDesignerInstallationController; + + @Test + void testGetProductDesignerInstallationByProductId() { + List models = List.of(new DesignerInstallation(DESIGNER_VERSION, 5)); + Mockito.when(productDesignerInstallationService.findByProductId(Mockito.anyString())).thenReturn(models); + ResponseEntity> result = productDesignerInstallationController.getProductDesignerInstallationByProductId("portal"); + Assertions.assertEquals(HttpStatus.OK, result.getStatusCode()); + Assertions.assertEquals(1, Objects.requireNonNull(result.getBody()).size()); + Assertions.assertEquals(DESIGNER_VERSION, result.getBody().get(0).getDesignerVersion()); + Assertions.assertEquals(5, result.getBody().get(0).getNumberOfDownloads()); + Assertions.assertEquals(models, result.getBody()); + } +} diff --git a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java index 766a1611f..05f37b299 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java @@ -14,6 +14,7 @@ import com.axonivy.market.constants.RequestMappingConstants; import com.axonivy.market.entity.ProductJsonContent; import com.axonivy.market.model.VersionAndUrlModel; +import com.axonivy.market.service.ProductDesignerInstallationService; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -41,6 +42,9 @@ class ProductDetailsControllerTest { @Mock VersionService versionService; + @Mock + ProductDesignerInstallationService productDesignerInstallationService; + @Mock private ProductDetailModelAssembler detailModelAssembler; @@ -116,9 +120,9 @@ void testFindProductVersionsById() { @Test void testSyncInstallationCount() { - when(productService.updateInstallationCountForProduct("google-maps-connector")).thenReturn(1); + when(productService.updateInstallationCountForProduct("google-maps-connector", "10.0.20")).thenReturn(1); - var result = productDetailsController.syncInstallationCount("google-maps-connector"); + var result = productDetailsController.syncInstallationCount("google-maps-connector", "10.0.20"); assertEquals(1, result.getBody()); } 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 4d9fb26db..3938e5d54 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,6 +3,7 @@ import com.axonivy.market.BaseSetup; import com.axonivy.market.constants.MongoDBConstants; import com.axonivy.market.entity.Product; +import com.axonivy.market.entity.ProductDesignerInstallation; import com.axonivy.market.repository.ProductModuleContentRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -132,4 +133,10 @@ void testUpdateInitialCount() { repo.updateInitialCount(ID, initialCount); verify(mongoTemplate).updateFirst(any(Query.class), eq(new Update().inc("InstallationCount", initialCount).set("SynchronizedInstallationCount", true)), eq(Product.class)); } + + @Test + void testIncreaseInstallationCountForProductByDesignerVersion() { + repo.increaseInstallationCountForProductByDesignerVersion("portal", "10.0.20"); + verify(mongoTemplate).upsert(any(Query.class), any(Update.class), eq(ProductDesignerInstallation.class)); + } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImplTest.java new file mode 100644 index 000000000..6e8998fef --- /dev/null +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductDesignerInstallationServiceImplTest.java @@ -0,0 +1,44 @@ +package com.axonivy.market.service.impl; + +import com.axonivy.market.BaseSetup; +import com.axonivy.market.entity.ProductDesignerInstallation; +import com.axonivy.market.model.DesignerInstallation; +import com.axonivy.market.repository.ProductDesignerInstallationRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ProductDesignerInstallationServiceImplTest extends BaseSetup { + private List mockResultReturn; + @Mock + private ProductDesignerInstallationRepository productDesignerInstallationRepository; + + @InjectMocks + private ProductDesignerInstallationServiceImpl productDesignerInstallationServiceImpl; + + @BeforeEach + public void setup() { + mockResultReturn = createProductDesignerInstallationsMock(); + } + + @Test + void testFindByProductId() { + when(productDesignerInstallationRepository.findByProductId(any(), any())).thenReturn(this.mockResultReturn); + List results = productDesignerInstallationServiceImpl.findByProductId(BaseSetup.SAMPLE_PRODUCT_ID); + assertEquals(2,results.size()); + assertEquals("10.0.22", results.get(0).getDesignerVersion()); + assertEquals(50, results.get(0).getNumberOfDownloads()); + assertEquals("11.4.0", results.get(1).getDesignerVersion()); + assertEquals(30, results.get(1).getNumberOfDownloads()); + } +} 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 127eaa577..5e473b619 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 @@ -142,13 +142,17 @@ public void setup() { @Test void testUpdateInstallationCountForProduct() { - int result = productService.updateInstallationCountForProduct(null); + String designerVersion = "10.0.20"; + int result = productService.updateInstallationCountForProduct(null, designerVersion); assertEquals(0, result); Product product = mockProduct(); when(productRepository.getProductById(product.getId())).thenReturn(product); when(productRepository.increaseInstallationCount(product.getId())).thenReturn(31); - result = productService.updateInstallationCountForProduct(product.getId()); + result = productService.updateInstallationCountForProduct(product.getId(), designerVersion); + assertEquals(31, result); + + result = productService.updateInstallationCountForProduct(product.getId(), ""); assertEquals(31, result); } diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts index c96440d10..825a955c0 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.spec.ts @@ -16,7 +16,6 @@ describe('ProductVersionActionComponent', () => { let component: ProductDetailVersionActionComponent; let fixture: ComponentFixture; let productServiceMock: any; - let elementRef: MockElementRef; beforeEach(() => { productServiceMock = jasmine.createSpyObj('ProductService', [ diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.ts index 2304f195b..4236a2867 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.ts @@ -1,10 +1,14 @@ import { AfterViewInit, - Component, computed, - ElementRef, EventEmitter, + Component, + computed, + ElementRef, + EventEmitter, inject, Input, - model, Output, Signal, + model, + Output, + Signal, signal, WritableSignal } from '@angular/core'; @@ -27,7 +31,12 @@ const delayTimeBeforeHideMessage = 2000; @Component({ selector: 'app-product-version-action', standalone: true, - imports: [CommonModule, TranslateModule, FormsModule, CommonDropdownComponent], + imports: [ + CommonModule, + TranslateModule, + FormsModule, + CommonDropdownComponent + ], templateUrl: './product-detail-version-action.component.html', styleUrl: './product-detail-version-action.component.scss' }) @@ -39,10 +48,10 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { @Input() product!: ProductDetail; selectedVersion = model(''); versions: WritableSignal = signal([]); - versionDropdown : Signal = computed(() => { + versionDropdown: Signal = computed(() => { return this.versions().map(version => ({ value: version, - label: version, + label: version })); }); metaDataJsonUrl = model(''); @@ -55,7 +64,7 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { isInvalidInstallationEnvironment = signal(false); designerVersion = ''; selectedArtifact: string | undefined = ''; - selectedArtifactName:string | undefined = ''; + selectedArtifactName: string | undefined = ''; versionMap: Map = new Map(); routingQueryParamService = inject(RoutingQueryParamService); @@ -75,7 +84,6 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { this.isDesignerEnvironment.set( this.routingQueryParamService.isDesignerEnv() ); - } getInstallationTooltipText() { @@ -86,11 +94,11 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { (minimum version 9.2.0)

`; } - onSelectVersion(version : string) { + onSelectVersion(version: string) { this.selectedVersion.set(version); this.artifacts.set(this.versionMap.get(this.selectedVersion()) ?? []); this.artifacts().forEach(artifact => { - if(artifact.name) { + if (artifact.name) { artifact.label = artifact.name; } }); @@ -148,19 +156,27 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { }); } - getVersionInDesigner(): void { if (this.versions().length === 0) { - this.productService.sendRequestToGetProductVersionsForDesigner(this.productId - ).subscribe(data => { - const versionMap = data.map(dataVersionAndUrl => dataVersionAndUrl.version).map(version => VERSION.displayPrefix.concat(version)); - data.forEach(dataVersionAndUrl => { - const currentVersion = VERSION.displayPrefix.concat(dataVersionAndUrl.version); - const versionAndUrl: ItemDropdown = { value: currentVersion, label: currentVersion, metaDataJsonUrl: dataVersionAndUrl.url }; - this.versionDropdownInDesigner.push(versionAndUrl); + this.productService + .sendRequestToGetProductVersionsForDesigner(this.productId) + .subscribe(data => { + const versionMap = data + .map(dataVersionAndUrl => dataVersionAndUrl.version) + .map(version => VERSION.displayPrefix.concat(version)); + data.forEach(dataVersionAndUrl => { + const currentVersion = VERSION.displayPrefix.concat( + dataVersionAndUrl.version + ); + const versionAndUrl: ItemDropdown = { + value: currentVersion, + label: currentVersion, + metaDataJsonUrl: dataVersionAndUrl.url + }; + this.versionDropdownInDesigner.push(versionAndUrl); + }); + this.versions.set(versionMap); }); - this.versions.set(versionMap); - }); } } @@ -182,7 +198,10 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { onUpdateInstallationCount() { this.productService - .sendRequestToUpdateInstallationCount(this.productId) + .sendRequestToUpdateInstallationCount( + this.productId, + this.routingQueryParamService.getDesignerVersionFromCookie() + ) .subscribe((data: number) => this.installationCount.emit(data)); } @@ -191,5 +210,4 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { this.onUpdateInstallationCount(); } } - } diff --git a/marketplace-ui/src/app/modules/product/product.service.spec.ts b/marketplace-ui/src/app/modules/product/product.service.spec.ts index f9d3ff539..212ca073a 100644 --- a/marketplace-ui/src/app/modules/product/product.service.spec.ts +++ b/marketplace-ui/src/app/modules/product/product.service.spec.ts @@ -215,12 +215,13 @@ describe('ProductService', () => { it('sendRequestToUpdateInstallationCount', () => { const productId = "google-maps-connector"; + const designerVersion = "10.0.0"; - service.sendRequestToUpdateInstallationCount(productId).subscribe(response => { + service.sendRequestToUpdateInstallationCount(productId, designerVersion).subscribe(response => { expect(response).toBe(3); }); - const req = httpMock.expectOne(`api/product-details/installationcount/${productId}`); + const req = httpMock.expectOne(`api/product-details/installationcount/${productId}?designerVersion=${designerVersion}`); expect(req.request.method).toBe('PUT'); expect(req.request.headers.get('X-Requested-By')).toBe('ivy'); req.flush(3); @@ -241,4 +242,5 @@ describe('ProductService', () => { expect(req.request.headers.get('X-Requested-By')).toBe('ivy'); req.flush([{ version: '10.0.2' }, {version: '10.0.1'}, {version: '10.0.0'}]); }); + }); diff --git a/marketplace-ui/src/app/modules/product/product.service.ts b/marketplace-ui/src/app/modules/product/product.service.ts index f28ec3343..9ecbebff4 100644 --- a/marketplace-ui/src/app/modules/product/product.service.ts +++ b/marketplace-ui/src/app/modules/product/product.service.ts @@ -76,14 +76,15 @@ export class ProductService { ); } - sendRequestToUpdateInstallationCount(productId: string) { + sendRequestToUpdateInstallationCount(productId: string, designerVersion: string) { const url = 'api/product-details/installationcount/' + productId; - return this.httpClient.put(url, null, { headers: { 'X-Requested-By': 'ivy' } }); + const headers = { 'X-Requested-By': 'ivy' }; + const params = new HttpParams().append('designerVersion', designerVersion); + return this.httpClient.put(url, null, { headers, params }); } sendRequestToGetProductVersionsForDesigner(productId: string) { const url = `api/product-details/${productId}/designerversions`; return this.httpClient.get(url, { headers: { 'X-Requested-By': 'ivy' } }); } - }