diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java index 4a9bef8ed..069f93b9d 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/ProductJsonConstants.java @@ -15,6 +15,7 @@ public class ProductJsonConstants { public static final String MAVEN_IMPORT_INSTALLER_ID = "maven-import"; public static final String MAVEN_DROPIN_INSTALLER_ID = "maven-dropins"; public static final String MAVEN_DEPENDENCY_INSTALLER_ID = "maven-dependency"; + public static final String CUSTOM_ORDER = "customOrder"; private ProductJsonConstants() { } diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java index f35347a6f..be28fe339 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java @@ -90,13 +90,6 @@ public ResponseEntity syncProducts(@RequestHeader(value = CommonConstan return new ResponseEntity<>(message, HttpStatus.OK); } - @GetMapping(CUSTOM_SORT) - public ResponseEntity> getCustomSortProducts(@RequestHeader(value = CommonConstants.AUTHORIZATION) String authorizationHeader) { - String token = getBearerToken(authorizationHeader); - gitHubService.validateUserOrganization(token, GitHubConstants.AXONIVY_MARKET_ORGANIZATION_NAME); - return new ResponseEntity<>(productService.getCustomSortProduct(), HttpStatus.OK); - } - @PostMapping(CUSTOM_SORT) public ResponseEntity createCustomSortProducts(@RequestHeader(value = CommonConstants.AUTHORIZATION) String authorizationHeader, @RequestBody @Valid ProductCustomSortRequest productCustomSortRequest) { 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 c1ec15ded..b567a353c 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 @@ -59,6 +59,7 @@ public class Product implements Serializable { private List productModuleContents; private List artifacts; private Boolean synchronizedInstallationCount; + private Integer customOrder; @Override public int hashCode() { diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/ProductCustomSort.java b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductCustomSort.java index f6117907d..aa2d2fa23 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/entity/ProductCustomSort.java +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductCustomSort.java @@ -16,6 +16,5 @@ @NoArgsConstructor @Document(PRODUCT_CUSTOM_SORT) public class ProductCustomSort { - private List orderedListOfProducts; private String ruleForRemainder; } diff --git a/marketplace-service/src/main/java/com/axonivy/market/enums/SortDirection.java b/marketplace-service/src/main/java/com/axonivy/market/enums/SortDirection.java deleted file mode 100644 index 34aa5f8d7..000000000 --- a/marketplace-service/src/main/java/com/axonivy/market/enums/SortDirection.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.axonivy.market.enums; - -import com.axonivy.market.exceptions.model.InvalidParamException; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.apache.commons.lang3.StringUtils; - -@Getter -@AllArgsConstructor -public enum SortDirection { - ASC("asc"), - DESC("desc"); - - private String direction; - - public static SortDirection of(String direction) { - direction = StringUtils.isBlank(direction) ? direction : direction.trim(); - for (var sortDirection : values()) { - if (StringUtils.equalsIgnoreCase(sortDirection.direction, direction)) { - return sortDirection; - } - } - throw new InvalidParamException(ErrorCode.SORT_DIRECTION_INVALID, "SortDirection: " + direction); - } -} \ No newline at end of file diff --git a/marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java b/marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java index 303c57b2b..275d7ec8f 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java +++ b/marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java @@ -4,15 +4,19 @@ import lombok.AllArgsConstructor; import lombok.Getter; import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Sort; @Getter @AllArgsConstructor public enum SortOption { - POPULARITY("popularity", "installationCount"), ALPHABETICALLY("alphabetically", "names"), - RECENT("recent", "newestPublishedDate"); + POPULARITY("popularity", "installationCount", Sort.Direction.DESC), + ALPHABETICALLY("alphabetically", "names", Sort.Direction.ASC), + RECENT("recent", "newestPublishedDate", Sort.Direction.DESC), + STANDARD("standard", "customOrder", Sort.Direction.DESC); private String option; private String code; + private Sort.Direction direction; public static SortOption of(String option) { option = StringUtils.isBlank(option) ? option : option.trim(); diff --git a/marketplace-service/src/main/java/com/axonivy/market/model/ProductCustomSortRequest.java b/marketplace-service/src/main/java/com/axonivy/market/model/ProductCustomSortRequest.java index a919ec08f..806ca1f20 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/model/ProductCustomSortRequest.java +++ b/marketplace-service/src/main/java/com/axonivy/market/model/ProductCustomSortRequest.java @@ -17,9 +17,6 @@ public class ProductCustomSortRequest { @NotEmpty(message = "orderedListOfProducts must not be empty") private List orderedListOfProducts; - @NotBlank(message = "sortRuleForRemainder must not be null or blank") - private String sortRuleForRemainder; - - @NotBlank(message = "sortDirectionForRemainder must not be null or blank") - private String sortDirectionForRemainder; + @NotBlank(message = "ruleForRemainder must not be null or blank") + private String ruleForRemainder; } diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java index 8c9bea693..f3bb81783 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.repository.Aggregation; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; @@ -28,7 +29,4 @@ public interface ProductRepository extends MongoRepository { @Query("{ $or: [ { 'names.?1': { $regex: ?0, $options: 'i' } }, { 'shortDescriptions.?1': { $regex: ?0, $options: 'i' } } ] }") Page searchByNameOrShortDescriptionRegex(String keyword, String language, Pageable unifiedPageabe); - - @Query(value = "{ $and: [{ type: ?0 }, { $addFields: { customOrder: { $switch: { branches: [ { case: { $eq: [_id, customIdList] }, then: 1 } ], default: 3 } } } } , { $sort: { customOrder: 1, newestPublishedDate: sortDirection } } ] }", count = true) - Page findByTypeAndCustomOrderAndSort(String type, List customIdList, Sort.Direction sortDirection, Pageable pageable); } 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 82e3a7eb4..2c89d783d 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 @@ -22,6 +22,4 @@ public interface ProductService { void clearAllProducts(); void addCustomSortProduct(ProductCustomSortRequest customSort) throws InvalidParamException; - - List getCustomSortProduct(); } 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 115c44225..ddcfdc849 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 @@ -2,8 +2,15 @@ import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.constants.GitHubConstants; -import com.axonivy.market.entity.*; -import com.axonivy.market.enums.*; +import com.axonivy.market.constants.ProductJsonConstants; +import com.axonivy.market.entity.GitHubRepoMeta; +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.SortOption; +import com.axonivy.market.enums.TypeOption; import com.axonivy.market.exceptions.model.InvalidParamException; import com.axonivy.market.factory.ProductFactory; import com.axonivy.market.github.model.GitHubFile; @@ -33,6 +40,9 @@ 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; @@ -62,6 +72,8 @@ public class ProductServiceImpl implements ProductService { private final GitHubService gitHubService; private final ProductCustomSortRepository productCustomSortRepository; + private final MongoTemplate mongoTemplate; + private GHCommit lastGHCommit; private GitHubRepoMeta marketRepoMeta; private final ObjectMapper mapper = new ObjectMapper(); @@ -71,15 +83,18 @@ 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, - GHAxonIvyProductRepoService axonIvyProductRepoService, GitHubRepoMetaRepository gitHubRepoMetaRepository, - GitHubService gitHubService, ProductCustomSortRepository productCustomSortRepository) { + GHAxonIvyProductRepoService axonIvyProductRepoService, GitHubRepoMetaRepository gitHubRepoMetaRepository, + GitHubService gitHubService, ProductCustomSortRepository productCustomSortRepository, + MongoTemplate mongoTemplate) { this.productRepository = productRepository; this.axonIvyMarketRepoService = axonIvyMarketRepoService; this.axonIvyProductRepoService = axonIvyProductRepoService; this.gitHubRepoMetaRepository = gitHubRepoMetaRepository; this.gitHubService = gitHubService; this.productCustomSortRepository = productCustomSortRepository; + this.mongoTemplate = mongoTemplate; } @Override @@ -140,7 +155,8 @@ private void syncInstallationCountWithProduct(Product product) { try { String installationCounts = Files.readString(Paths.get(installationCountPath)); Map mapping = mapper.readValue(installationCounts, - new TypeReference>(){}); + new TypeReference>() { + }); List keyList = mapping.keySet().stream().toList(); int currentInstallationCount = keyList.contains(product.getId()) ? mapping.get(product.getId()) @@ -244,15 +260,32 @@ private Pageable refinePagination(String language, Pageable pageable) { if (pageable != null) { List orders = new ArrayList<>(); for (var sort : pageable.getSort()) { - final var sortOption = SortOption.of(sort.getProperty()); - Order order = new Order(sort.getDirection(), sortOption.getCode(language)); + SortOption sortOption = SortOption.of(sort.getProperty()); + Order order = createOrder(sortOption, language); orders.add(order); + if (SortOption.STANDARD.equals(sortOption)) { + orders.add(getExtensionOrder(language)); + } } pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(orders)); } return pageRequest; } + private Order createOrder(SortOption sortOption, String language) { + return new Order(sortOption.getDirection(), sortOption.getCode(language)); + } + + private Order getExtensionOrder(String language) { + List customSorts = productCustomSortRepository.findAll(); + + if (!customSorts.isEmpty()) { + SortOption sortOptionExtension = SortOption.of(customSorts.get(0).getRuleForRemainder()); + return createOrder(sortOptionExtension, language); + } + return createOrder(SortOption.POPULARITY, language); + } + private boolean isLastGithubCommitCovered() { boolean isLastCommitCovered = false; long lastCommitTime = 0L; @@ -352,39 +385,36 @@ public void clearAllProducts() { @Override public void addCustomSortProduct(ProductCustomSortRequest customSort) throws InvalidParamException { - refineRuleInCustomSort(customSort.getSortRuleForRemainder(), customSort.getSortDirectionForRemainder()); + SortOption.of(customSort.getRuleForRemainder()); - ProductCustomSort productCustomSort = new ProductCustomSort(); - productCustomSort.setOrderedListOfProducts(refineOrderedListOfProductsInCustomSort(customSort.getOrderedListOfProducts())); - productCustomSort.setRuleForRemainder(customSort.getSortRuleForRemainder() + " " + customSort.getSortDirectionForRemainder()); + ProductCustomSort productCustomSort = new ProductCustomSort(customSort.getRuleForRemainder()); productCustomSortRepository.deleteAll(); + removeFieldFromAllProductDocuments(ProductJsonConstants.CUSTOM_ORDER); productCustomSortRepository.save(productCustomSort); + productRepository.saveAll(refineOrderedListOfProductsInCustomSort(customSort.getOrderedListOfProducts())); } - @Override - public List getCustomSortProduct() { - return List.of(); - } - - private void refineRuleInCustomSort(String sortOption, String sortDirection) throws InvalidParamException { - SortOption.of(sortOption); - SortDirection.of(sortDirection); - } + private List refineOrderedListOfProductsInCustomSort(List orderedListOfProducts) + throws InvalidParamException { + List productEntries = new ArrayList<>(); - private List refineOrderedListOfProductsInCustomSort(List orderedListOfProducts) throws InvalidParamException { - List productSortEntries = new ArrayList<>(); - - for (int i = 0; i < orderedListOfProducts.size(); i++) { - String productId = orderedListOfProducts.get(i); + int descendingOrder = orderedListOfProducts.size(); + for (String productId : orderedListOfProducts) { Optional productOptional = productRepository.findById(productId); if (productOptional.isEmpty()) { throw new InvalidParamException(ErrorCode.PRODUCT_NOT_FOUND, "Not found product with id: " + productId); } - - productSortEntries.add(new ProductSortEntry(productId, i + 1)); + Product product = productOptional.get(); + product.setCustomOrder(descendingOrder--); + productEntries.add(product); } - return productSortEntries; + return productEntries; + } + + public void removeFieldFromAllProductDocuments(String fieldName) { + Update update = new Update().unset(fieldName); + mongoTemplate.updateMulti(new Query(), update, Product.class); } } diff --git a/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.ts b/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.ts index 8961dc021..c2d9cfc66 100644 --- a/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.ts +++ b/marketplace-ui/src/app/modules/product/product-filter/product-filter.component.ts @@ -21,7 +21,7 @@ export class ProductFilterComponent { selectedType = TypeOption.All_TYPES; types = FILTER_TYPES; - selectedSort: SortOption = SortOption.POPULARITY; + selectedSort: SortOption = SortOption.STANDARD; sorts = SORT_TYPES; searchText = ''; diff --git a/marketplace-ui/src/app/modules/product/product.component.ts b/marketplace-ui/src/app/modules/product/product.component.ts index 3117e6d7b..ed6910d69 100644 --- a/marketplace-ui/src/app/modules/product/product.component.ts +++ b/marketplace-ui/src/app/modules/product/product.component.ts @@ -52,7 +52,7 @@ export class ProductComponent implements AfterViewInit, OnDestroy { criteria: Criteria = { search: '', type: TypeOption.All_TYPES, - sort: SortOption.POPULARITY, + sort: SortOption.STANDARD, language: Language.EN }; responseLink!: Link; diff --git a/marketplace-ui/src/app/shared/constants/common.constant.ts b/marketplace-ui/src/app/shared/constants/common.constant.ts index 9df949ebb..55b913434 100644 --- a/marketplace-ui/src/app/shared/constants/common.constant.ts +++ b/marketplace-ui/src/app/shared/constants/common.constant.ts @@ -97,6 +97,10 @@ export const FILTER_TYPES = [ ]; export const SORT_TYPES = [ + { + value: SortOption.STANDARD, + label: 'common.sort.value.standard' + }, { value: SortOption.POPULARITY, label: 'common.sort.value.popularity' diff --git a/marketplace-ui/src/app/shared/enums/sort-option.enum.ts b/marketplace-ui/src/app/shared/enums/sort-option.enum.ts index d2b3e179f..d2d82adf0 100644 --- a/marketplace-ui/src/app/shared/enums/sort-option.enum.ts +++ b/marketplace-ui/src/app/shared/enums/sort-option.enum.ts @@ -1,4 +1,5 @@ export enum SortOption { + STANDARD = 'standard', POPULARITY = 'popularity', ALPHABETICALLY = 'alphabetically', RECENT = 'recent' diff --git a/marketplace-ui/src/assets/i18n/de.yaml b/marketplace-ui/src/assets/i18n/de.yaml index 64524d90a..2d5e7d006 100644 --- a/marketplace-ui/src/assets/i18n/de.yaml +++ b/marketplace-ui/src/assets/i18n/de.yaml @@ -29,6 +29,7 @@ common: popularity: Beliebtheit alphabetically: Alphabetisch recent: Datum der Veröffentlichung + standard: Standard newest: Neuestes oldest: Älteste highest: Höchste diff --git a/marketplace-ui/src/assets/i18n/en.yaml b/marketplace-ui/src/assets/i18n/en.yaml index 70876ac43..e1885ba82 100644 --- a/marketplace-ui/src/assets/i18n/en.yaml +++ b/marketplace-ui/src/assets/i18n/en.yaml @@ -33,6 +33,7 @@ common: popularity: Popularity alphabetically: Alphabetically recent: Recent + standard: Standard newest: Newest oldest: Oldest highest: Highest