diff --git a/marketplace-service/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java b/marketplace-service/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java index 1bd71b286..87fdc7472 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java +++ b/marketplace-service/src/main/java/com/axonivy/market/MarketplaceServiceApplication.java @@ -41,7 +41,7 @@ private List syncProductData() { var watch = new StopWatch(); log.warn("Synchronizing Market repo: Started synchronizing data for Axon Ivy Market repo"); watch.start(); - List syncedProductIds = productService.syncLatestDataFromMarketRepo(); + List syncedProductIds = productService.syncLatestDataFromMarketRepo(false); if (ObjectUtils.isEmpty(syncedProductIds)) { log.warn("Synchronizing Market repo: Nothing updated"); } else { 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 1732722a6..ed4acf702 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 @@ -41,7 +41,7 @@ public ProductDetailModel toModel(Product product, String version, String reques } private ProductDetailModel createModel(Product product, String version, String requestPath) { - ResponseEntity selfLinkWithTag; + ResponseEntity selfLinkWithVersion; ProductDetailModel model = instantiateModel(product); productModelAssembler.createResource(model, product); String productId = Optional.of(product).map(Product::getId).orElse(StringUtils.EMPTY); @@ -53,7 +53,7 @@ private ProductDetailModel createModel(Product product, String version, String r model.setMetaProductJsonUrl(link.getHref()); } - selfLinkWithTag = switch (requestPath) { + selfLinkWithVersion = switch (requestPath) { case RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION -> methodOn(ProductDetailsController.class).findBestMatchProductDetailsByVersion(productId, version); case RequestMappingConstants.BY_ID_AND_VERSION -> @@ -61,7 +61,7 @@ private ProductDetailModel createModel(Product product, String version, String r default -> methodOn(ProductDetailsController.class).findProductDetails(productId, false); }; - model.add(linkTo(selfLinkWithTag).withSelfRel()); + model.add(linkTo(selfLinkWithVersion).withSelfRel()); createDetailResource(model, product); return model; } @@ -89,6 +89,6 @@ private void createDetailResource(ProductDetailModel model, Product product) { linkTo(methodOn(ImageController.class).findImageById(product.getVendorImageDarkMode())).withSelfRel(); model.setVendorImageDarkMode(vendorDarkModeLink.getHref()); } + model.setMavenDropins(product.isMavenDropins()); } - } diff --git a/marketplace-service/src/main/java/com/axonivy/market/comparator/MavenVersionComparator.java b/marketplace-service/src/main/java/com/axonivy/market/comparator/MavenVersionComparator.java index b707019a7..974fd6fdf 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/comparator/MavenVersionComparator.java +++ b/marketplace-service/src/main/java/com/axonivy/market/comparator/MavenVersionComparator.java @@ -3,10 +3,7 @@ import com.axonivy.market.constants.CommonConstants; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; -import org.kohsuke.github.GHTag; -import org.springframework.util.CollectionUtils; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -24,28 +21,6 @@ public class MavenVersionComparator { private MavenVersionComparator() { } - public static GHTag findHighestTag(List ghTags) { - if (CollectionUtils.isEmpty(ghTags)) { - return null; - } - String highestVersion = findHighestMavenVersion(ghTags.stream().map(GHTag::getName).toList()); - return ghTags.stream().filter(tag -> tag.getName().equals(highestVersion)).findAny().orElse(null); - } - - public static String findHighestMavenVersion(List versions) { - if (CollectionUtils.isEmpty(versions)) { - return null; - } - - String highestVersion = versions.get(0); - for (var version : versions) { - if (compare(version, highestVersion) > EQUAL) { - highestVersion = version; - } - } - return highestVersion; - } - public static int compare(String version, String otherVersion) { version = stripLeadingChars(version); otherVersion = stripLeadingChars(otherVersion); diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/GitHubConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/GitHubConstants.java index 3419c083f..41e42393e 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/GitHubConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/GitHubConstants.java @@ -12,10 +12,6 @@ public class GitHubConstants { public static final String GITHUB_PROVIDER_NAME = "GitHub"; public static final String GITHUB_GET_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token"; public static final String README_FILE_LOCALE_REGEX = "_(..)"; - public static final String STANDARD_TAG_PREFIX = "v"; - public static final String COMMON_IMAGES_FOLDER_NAME = "images"; - public static final String MS_GRAPH_PRODUCT_DIRECTORY = "msgraph-connector-product"; - public static final String MG_GRAPH_IMAGES_FOR_SETUP_FILE = "doc"; @NoArgsConstructor(access = AccessLevel.PRIVATE) public static class Json { diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/MavenConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/MavenConstants.java index 29fb3e580..22058d38b 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/MavenConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/MavenConstants.java @@ -1,5 +1,9 @@ package com.axonivy.market.constants; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class MavenConstants { public static final String SNAPSHOT_VERSION = "SNAPSHOT"; public static final String SNAPSHOT_RELEASE_POSTFIX = "-" + SNAPSHOT_VERSION; @@ -20,8 +24,4 @@ public class MavenConstants { public static final String SNAPSHOT_LAST_UPDATED_DATE_TIME_FORMAT = "yyyyMMdd.HHmmss"; public static final String VALUE_TAG = "value"; public static final String DEFAULT_PRODUCT_FOLDER_TYPE = "zip"; - - - private MavenConstants() { - } } 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 d4116b9fb..69b726c86 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 @@ -11,10 +11,8 @@ public class MongoDBConstants { public static final String SYNCHRONIZED_INSTALLATION_COUNT = "SynchronizedInstallationCount"; public static final String PRODUCT_ID = "productId"; public static final String DESIGNER_VERSION = "designerVersion"; - public static final String TAG = "tag"; - public static final String PROJECT_KEY = "$project"; + public static final String VERSION = "version"; public static final String RELEASED_VERSIONS = "releasedVersions"; public static final String ARTIFACTS = "artifacts"; - public static final String MAVEN_VERSIONS = "mavenVersions"; public static final String ARTIFACTS_DOC = "artifacts.doc"; } diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/ReadmeConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/ReadmeConstants.java index d2e11abd7..1e1e64b5c 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/ReadmeConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/ReadmeConstants.java @@ -5,10 +5,7 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class ReadmeConstants { - public static final String IMAGES = "images"; - public static final String README_FILE = "README.md"; public static final String README_FILE_NAME = "README"; public static final String DEMO_PART = "## Demo"; public static final String SETUP_PART = "## Setup"; - public static final String SETUP_FILE = "setup.md"; } diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestParamConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestParamConstants.java index 516652a5f..503936b62 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestParamConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestParamConstants.java @@ -19,5 +19,4 @@ public class RequestParamConstants { public static final String ARTIFACT = "artifact"; public static final String MARKET_ITEM_PATH = "marketItemPath"; public static final String OVERRIDE_MARKET_ITEM_PATH = "overrideMarketItemPath"; - } 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 25e360c0e..12ecba55a 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 @@ -30,9 +30,6 @@ import org.springframework.hateoas.PagedModel; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; - -import java.util.List; - import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -44,6 +41,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + import static com.axonivy.market.constants.RequestMappingConstants.*; import static com.axonivy.market.constants.RequestParamConstants.*; import static org.springframework.http.HttpHeaders.AUTHORIZATION; @@ -100,13 +99,10 @@ public ResponseEntity syncProducts(@RequestHeader(value = AUTHORIZATION String token = AuthorizationUtils.getBearerToken(authorizationHeader); gitHubService.validateUserInOrganizationAndTeam(token, GitHubConstants.AXONIVY_MARKET_ORGANIZATION_NAME, GitHubConstants.AXONIVY_MARKET_TEAM_NAME); - if (Boolean.TRUE.equals(resetSync)) { - productService.clearAllProducts(); - } var stopWatch = new StopWatch(); stopWatch.start(); - List syncedProductIds = productService.syncLatestDataFromMarketRepo(); + List syncedProductIds = productService.syncLatestDataFromMarketRepo(resetSync); var message = new Message(); message.setHelpCode(ErrorCode.SUCCESSFUL.getCode()); message.setHelpText(ErrorCode.SUCCESSFUL.getHelpText()); 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 da2608706..9167c0930 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 @@ -118,7 +118,7 @@ public ResponseEntity> findProductVersionsById( "Ivy designer") public ResponseEntity> findProductJsonContent(@PathVariable(ID) String productId, @PathVariable(VERSION) String version) { - Map productJsonContent = versionService.getProductJsonContentByIdAndTag(productId, version); + Map productJsonContent = versionService.getProductJsonContentByIdAndVersion(productId, version); return new ResponseEntity<>(productJsonContent, HttpStatus.OK); } 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 7b998e643..b62f89397 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 @@ -77,6 +77,8 @@ public class Product implements Serializable { private Date updatedAt; @Transient private String bestMatchVersion; + @Transient + private boolean isMavenDropins; @Override public int hashCode() { diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/ProductJsonContent.java b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductJsonContent.java index 181babb66..ab10d7d99 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/entity/ProductJsonContent.java +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductJsonContent.java @@ -25,6 +25,10 @@ public class ProductJsonContent { @JsonIgnore private String id; private String version; + /** + * @deprecated + */ + @Deprecated(forRemoval = true, since = "1.5.0") private String relatedTag; private String productId; private String name; 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 c2542768a..52a5f7946 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 @@ -31,10 +31,20 @@ public class ProductModuleContent implements Serializable { private static final long serialVersionUID = 1L; @Schema(description = "product Id (from meta.json)", example = "portal") private String productId; + /** + * @deprecated + */ + @Deprecated(forRemoval = true, since = "1.5.0") @Schema(description = "Target release tag", example = "v10.0.25") private String tag; + /** + * @deprecated + */ + @Deprecated(forRemoval = true, since = "1.5.0") @Schema(description = "Versions in maven", example = "10.0.25-SNAPSHOT") private Set mavenVersions; + @Schema(description = "Maven version", example = "10.0.25") + private String version; @Schema(description = "Product detail description content ", example = "{ \"de\": \"E-Sign-Konnektor\", \"en\": \"E-sign connector\" }") private Map description; diff --git a/marketplace-service/src/main/java/com/axonivy/market/enums/NonStandardProduct.java b/marketplace-service/src/main/java/com/axonivy/market/enums/NonStandardProduct.java deleted file mode 100644 index fa2c08c15..000000000 --- a/marketplace-service/src/main/java/com/axonivy/market/enums/NonStandardProduct.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.axonivy.market.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.apache.commons.lang3.StringUtils; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static com.axonivy.market.constants.GitHubConstants.COMMON_IMAGES_FOLDER_NAME; - -@Getter -@AllArgsConstructor -public enum NonStandardProduct { - PORTAL("portal", true, COMMON_IMAGES_FOLDER_NAME, "AxonIvyPortal/portal-product"), - MICROSOFT_REPO_NAME("msgraph-connector", false, COMMON_IMAGES_FOLDER_NAME, ""), - MICROSOFT_365("msgraph", false, COMMON_IMAGES_FOLDER_NAME, - "msgraph-connector-product/products/msgraph-connector"), // No meta.json - MICROSOFT_CALENDAR("msgraph-calendar", false, COMMON_IMAGES_FOLDER_NAME, - "msgraph-connector-product/products/msgraph-calendar"), // no fix product json - MICROSOFT_MAIL("msgraph-mail", false, COMMON_IMAGES_FOLDER_NAME, - "msgraph-connector-product/products/msgraph-mail"),// no fix product json - MICROSOFT_TEAMS("msgraph-chat", false, COMMON_IMAGES_FOLDER_NAME, - "msgraph-connector-product/products/msgraph-chat"),// no fix product json - MICROSOFT_TODO("msgraph-todo", false, COMMON_IMAGES_FOLDER_NAME, - "msgraph-connector-product/products/msgraph-todo"),// no fix product json - CONNECTIVITY_FEATURE("connectivity-demo", false, COMMON_IMAGES_FOLDER_NAME, - "connectivity/connectivity-demos-product"), - EMPLOYEE_ONBOARDING("employee-onboarding", false, COMMON_IMAGES_FOLDER_NAME, ""), // Invalid meta.json - ERROR_HANDLING("error-handling-demo", false, COMMON_IMAGES_FOLDER_NAME, - "error-handling/error-handling-demos-product"), - RULE_ENGINE_DEMOS("rule-engine-demo", false, COMMON_IMAGES_FOLDER_NAME, "rule-engine/rule-engine-demos-product"), - WORKFLOW_DEMO("workflow-demo", false, COMMON_IMAGES_FOLDER_NAME, "workflow/workflow-demos-product"), - HTML_DIALOG_DEMO("html-dialog-demo", false, COMMON_IMAGES_FOLDER_NAME, "html-dialog/html-dialog-demos-product"), - PROCESSING_VALVE_DEMO("processing-valve-demo", false, COMMON_IMAGES_FOLDER_NAME, ""),// no product json - ASPOSE_BARCODE("aspose-barcode-demo", false, "", "aspose-barcode-demo-product"), - DOC_FACTORY("doc-factory", false, "", "doc-factory-product"), - ASPOSE_EMAIL("aspose-email-demo", false, "", "aspose-email-demo-product"), - OPENAI_CONNECTOR("openai-connector", false, COMMON_IMAGES_FOLDER_NAME, "openai-connector-product"), - OPENAI_ASSISTANT("openai-assistant", false, "docs", "openai-assistant-product"), - // Non standard image folder name - EXCEL_IMPORTER("excel-importer", false, "doc", ""), - EXPRESS_IMPORTER("express-importer", false, "img", ""), - GRAPHQL_DEMO("graphql-demo", false, "assets", ""), - DEEPL_CONNECTOR("deepl-connector", false, "img", ""), - DEFAULT("", false, COMMON_IMAGES_FOLDER_NAME, ""); - - private final String id; - private final boolean isVersionTagNumberOnly; - private final String pathToImageFolder; - private final String pathToProductFolder; - private static final Map NON_STANDARD_PRODUCT_MAP; - - static { - NON_STANDARD_PRODUCT_MAP = Arrays.stream(NonStandardProduct.values()).collect( - Collectors.toMap(NonStandardProduct::getId, Function.identity())); - } - - public static NonStandardProduct findById(String id) { - return NON_STANDARD_PRODUCT_MAP.getOrDefault(id, DEFAULT); - } - - public static String findById(String id, String currentPath) { - String nonStandardPath = findById(id).pathToProductFolder; - return StringUtils.isNotBlank(nonStandardPath) ? nonStandardPath : currentPath; - } - - public static boolean isMsGraphProduct(String productId) { - return List.of(MICROSOFT_REPO_NAME.id, MICROSOFT_365.id, - MICROSOFT_CALENDAR.id, MICROSOFT_MAIL.id, MICROSOFT_TEAMS.id, MICROSOFT_TODO.id).contains(productId); - } -} \ No newline at end of file diff --git a/marketplace-service/src/main/java/com/axonivy/market/factory/ProductFactory.java b/marketplace-service/src/main/java/com/axonivy/market/factory/ProductFactory.java index 55e36e917..b234adc86 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/factory/ProductFactory.java +++ b/marketplace-service/src/main/java/com/axonivy/market/factory/ProductFactory.java @@ -75,7 +75,7 @@ public static Product mappingByMetaJSONFile(Product product, GHContent ghContent extractSourceUrl(product, meta); List artifacts = CollectionUtils.isEmpty( meta.getMavenArtifacts()) ? new ArrayList<>() : meta.getMavenArtifacts(); - artifacts.stream().forEach( + artifacts.forEach( artifact -> artifact.setInvalidArtifact(!artifact.getArtifactId().contains(meta.getId()))); product.setArtifacts(artifacts); product.setReleasedVersions(new ArrayList<>()); @@ -86,8 +86,6 @@ public static Product mappingByMetaJSONFile(Product product, GHContent ghContent public static void transferComputedPersistedDataToProduct(Product persisted, Product product) { product.setMarketDirectory(persisted.getMarketDirectory()); product.setCustomOrder(persisted.getCustomOrder()); - product.setNewestReleaseVersion(persisted.getNewestReleaseVersion()); - product.setReleasedVersions(persisted.getReleasedVersions()); product.setInstallationCount(persisted.getInstallationCount()); product.setSynchronizedInstallationCount(persisted.getSynchronizedInstallationCount()); } @@ -127,10 +125,9 @@ private static Meta jsonDecode(GHContent ghContent) throws IOException { } public static void mappingIdForProductModuleContent(ProductModuleContent content) { - if (StringUtils.isNotBlank(content.getProductId())) { - String version = StringUtils.isNotBlank( - content.getTag()) ? content.getTag() : content.getMavenVersions().stream().findAny().orElse(null); - content.setId(String.format(CommonConstants.ID_WITH_NUMBER_PATTERN, content.getProductId(), version)); + if (StringUtils.isNotBlank(content.getProductId()) && StringUtils.isNotBlank(content.getVersion())) { + content.setId( + String.format(CommonConstants.ID_WITH_NUMBER_PATTERN, content.getProductId(), content.getVersion())); } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java index 1ad461751..c869cd85c 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java +++ b/marketplace-service/src/main/java/com/axonivy/market/github/service/GHAxonIvyProductRepoService.java @@ -3,24 +3,13 @@ import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductModuleContent; import org.kohsuke.github.GHContent; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHTag; -import java.io.IOException; import java.util.List; -import java.util.Map; public interface GHAxonIvyProductRepoService { GHContent getContentFromGHRepoAndTag(String repoName, String filePath, String tagVersion); - List getAllTagsFromRepoName(String repoName) throws IOException; - - ProductModuleContent getReadmeAndProductContentsFromTag(Product product, GHRepository ghRepository, String tag); - void extractReadMeFileFromContents(Product product, List contents, ProductModuleContent productModuleContent); - - void updateSetupPartForProductModuleContent(Product product, - Map> moduleContents, String tag) throws IOException; } 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 69853795a..e5d79ee4c 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 @@ -1,48 +1,28 @@ package com.axonivy.market.github.service.impl; -import com.axonivy.market.bo.Artifact; -import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.constants.GitHubConstants; -import com.axonivy.market.constants.MavenConstants; -import com.axonivy.market.constants.ProductJsonConstants; import com.axonivy.market.constants.ReadmeConstants; import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductModuleContent; -import com.axonivy.market.enums.Language; -import com.axonivy.market.enums.NonStandardProduct; import com.axonivy.market.github.service.GHAxonIvyProductRepoService; import com.axonivy.market.github.service.GitHubService; import com.axonivy.market.github.util.GitHubUtils; import com.axonivy.market.service.ImageService; -import com.axonivy.market.service.ProductJsonContentService; import com.axonivy.market.util.ProductContentUtils; -import com.axonivy.market.util.VersionUtils; import lombok.extern.log4j.Log4j2; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.ObjectUtils; import org.kohsuke.github.GHContent; import org.kohsuke.github.GHOrganization; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHTag; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import static com.axonivy.market.constants.CommonConstants.IMAGE_ID_PREFIX; -import static com.axonivy.market.constants.GitHubConstants.MG_GRAPH_IMAGES_FOR_SETUP_FILE; -import static com.axonivy.market.constants.GitHubConstants.MS_GRAPH_PRODUCT_DIRECTORY; -import static com.axonivy.market.constants.ReadmeConstants.SETUP_FILE; -import static com.axonivy.market.util.ProductContentUtils.SETUP; @Log4j2 @Service @@ -50,20 +30,10 @@ public class GHAxonIvyProductRepoServiceImpl implements GHAxonIvyProductRepoServ private final GitHubService gitHubService; private final ImageService imageService; private GHOrganization organization; - private final ProductJsonContentService productJsonContentService; - public GHAxonIvyProductRepoServiceImpl(GitHubService gitHubService, ImageService imageService, - ProductJsonContentService productJsonContentService) { + public GHAxonIvyProductRepoServiceImpl(GitHubService gitHubService, ImageService imageService) { this.gitHubService = gitHubService; this.imageService = imageService; - this.productJsonContentService = productJsonContentService; - } - - private static GHContent getProductJsonFile(List contents) { - return contents.stream().filter(GHContent::isFile) - .filter(content -> ProductJsonConstants.PRODUCT_JSON_FILE.equals(content.getName())) - .findFirst() - .orElse(null); } @Override @@ -83,27 +53,6 @@ public GHOrganization getOrganization() throws IOException { return organization; } - @Override - public List getAllTagsFromRepoName(String repoName) throws IOException { - return getOrganization().getRepository(repoName).listTags().toList(); - } - - @Override - public ProductModuleContent getReadmeAndProductContentsFromTag(Product product, GHRepository ghRepository, - String tag) { - ProductModuleContent productModuleContent = ProductContentUtils.initProductModuleContent(product.getId(), tag, - new HashSet<>()); - try { - List contents = getProductFolderContents(product.getId(), ghRepository, tag); - updateDependencyContentsFromProductJson(productModuleContent, contents, product); - extractReadMeFileFromContents(product, contents, productModuleContent); - } catch (Exception e) { - log.error("Cannot get product.json content in {} - {}", ghRepository.getName(), e.getMessage()); - return null; - } - return productModuleContent; - } - public void extractReadMeFileFromContents(Product product, List contents, ProductModuleContent productModuleContent) { try { @@ -118,8 +67,6 @@ public void extractReadMeFileFromContents(Product product, List conte readmeContents = updateImagesWithDownloadUrl(product.getId(), contents, readmeContents); } ProductContentUtils.getExtractedPartsOfReadme(moduleContents, readmeContents, readmeFile.getName()); - updateSetupPartForProductModuleContent(product, moduleContents, - productModuleContent.getTag()); } ProductContentUtils.updateProductModuleTabContents(productModuleContent, moduleContents); } @@ -128,64 +75,11 @@ public void extractReadMeFileFromContents(Product product, List conte } } - @Override - public void updateSetupPartForProductModuleContent(Product product, - Map> moduleContents, String tag) throws IOException { - if (!NonStandardProduct.isMsGraphProduct(product.getId())) { - return; - } - - GHRepository ghRepository = gitHubService.getRepository(product.getRepositoryName()); - List contents = ghRepository.getDirectoryContent(MS_GRAPH_PRODUCT_DIRECTORY, tag); - - GHContent setupFile = contents.stream().filter(GHContent::isFile) - .filter(content -> content.getName().equalsIgnoreCase(SETUP_FILE)) - .findFirst().orElse(null); - - if (ObjectUtils.isNotEmpty(setupFile)) { - String setupContent = new String(setupFile.read().readAllBytes()); - if (ProductContentUtils.hasImageDirectives(setupContent)) { - List setupImagesFolder = - contents.stream().filter(content -> content.getName().equals(MG_GRAPH_IMAGES_FOR_SETUP_FILE)).toList(); - setupContent = updateImagesWithDownloadUrl(product.getId(), setupImagesFolder, setupContent); - } - - if (setupContent.contains(ReadmeConstants.SETUP_PART)) { - List extractSetupContent = List.of(setupContent.split(ReadmeConstants.SETUP_PART)); - setupContent = ProductContentUtils.removeFirstLine(extractSetupContent.get(1)); - } - ProductContentUtils.addLocaleContent(moduleContents, SETUP, setupContent, Language.EN.getValue()); - } - } - - private void updateDependencyContentsFromProductJson(ProductModuleContent productModuleContent, - List contents, Product product) throws IOException { - GHContent productJsonFile = getProductJsonFile(contents); - if (Objects.nonNull(productJsonFile)) { - List artifacts = GitHubUtils.convertProductJsonToMavenProductInfo(productJsonFile); - ProductContentUtils.updateProductModule(productModuleContent, artifacts); - String currentVersion = VersionUtils.convertTagToVersion(productModuleContent.getTag()); - String content = extractProductJsonContent(productJsonFile, productModuleContent.getTag()); - productJsonContentService.updateProductJsonContent(content, productModuleContent.getTag(), currentVersion, - ProductJsonConstants.VERSION_VALUE, product); - } - } - - public String extractProductJsonContent(GHContent ghContent, String tag) { - try { - InputStream contentStream = GitHubUtils.extractedContentStream(ghContent); - return IOUtils.toString(contentStream, StandardCharsets.UTF_8); - } catch (Exception exception) { - log.error("Cannot paste content of product.json {} at tag: {}", ghContent.getPath(), tag); - return null; - } - } - public String updateImagesWithDownloadUrl(String productId, List contents, String readmeContents) { List allContentOfImages = getAllImagesFromProductFolder(contents); Map imageUrls = new HashMap<>(); - allContentOfImages.forEach(content -> Optional.of(imageService.mappingImageFromGHContent(productId, content, false)) + allContentOfImages.forEach(content -> Optional.of(imageService.mappingImageFromGHContent(productId, content)) .ifPresent(image -> imageUrls.put(content.getName(), IMAGE_ID_PREFIX.concat(image.getId())))); return ProductContentUtils.replaceImageDirWithImageCustomId(imageUrls, readmeContents); } @@ -195,14 +89,4 @@ private List getAllImagesFromProductFolder(List productFol GitHubUtils.findImages(productFolderContents, images); return images; } - - private List getProductFolderContents(String productId, GHRepository ghRepository, String tag) - throws IOException { - String productFolderPath = ghRepository.getDirectoryContent(CommonConstants.SLASH, tag).stream() - .filter(GHContent::isDirectory).map(GHContent::getName) - .filter(content -> content.endsWith(MavenConstants.PRODUCT_ARTIFACT_POSTFIX)).findFirst().orElse(null); - productFolderPath = NonStandardProduct.findById(productId, productFolderPath); - - return ghRepository.getDirectoryContent(productFolderPath, tag); - } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/github/util/GitHubUtils.java b/marketplace-service/src/main/java/com/axonivy/market/github/util/GitHubUtils.java index 31326fc4c..0f0de6232 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/github/util/GitHubUtils.java +++ b/marketplace-service/src/main/java/com/axonivy/market/github/util/GitHubUtils.java @@ -2,7 +2,6 @@ import com.axonivy.market.bo.Artifact; import com.axonivy.market.constants.CommonConstants; -import com.axonivy.market.enums.NonStandardProduct; import com.axonivy.market.util.MavenUtils; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -67,14 +66,6 @@ public static String convertArtifactIdToName(String artifactId) { .collect(Collectors.joining(CommonConstants.SPACE_SEPARATOR)); } - public static String getNonStandardProductFilePath(String productId) { - return NonStandardProduct.findById(productId).getPathToProductFolder(); - } - - public static String getNonStandardImageFolder(String productId) { - return NonStandardProduct.findById(productId).getPathToImageFolder(); - } - public static String extractMessageFromExceptionMessage(String exceptionMessage) { String json = extractJson(exceptionMessage); String key = "\"message\":\""; diff --git a/marketplace-service/src/main/java/com/axonivy/market/model/ProductDetailModel.java b/marketplace-service/src/main/java/com/axonivy/market/model/ProductDetailModel.java index 74ece6e18..c0572fc6c 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/model/ProductDetailModel.java +++ b/marketplace-service/src/main/java/com/axonivy/market/model/ProductDetailModel.java @@ -46,6 +46,7 @@ public class ProductDetailModel extends ProductModel { private int installationCount; @Schema(description = "The api url to get metadata from product.json") private String metaProductJsonUrl; + private boolean isMavenDropins; @Override public int hashCode() { diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductModuleContentRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductModuleContentRepository.java index af4652af8..423f47a4b 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductModuleContentRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductModuleContentRepository.java @@ -4,5 +4,5 @@ public interface CustomProductModuleContentRepository { - List findTagsByProductId(String id); + List findVersionsByProductId(String id); } 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 fbedca61c..d00ff2af1 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 @@ -2,16 +2,17 @@ import com.axonivy.market.criteria.ProductSearchCriteria; import com.axonivy.market.entity.Product; -import com.axonivy.market.entity.ProductModuleContent; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import java.util.List; public interface CustomProductRepository { - Product getProductByIdWithTagOrVersion(String id, String tag); + Product getProductByIdAndVersion(String id, String version); - Product getProductById(String id); + Product getProductWithModuleContent(String id); + + Product findProductById(String id); List getReleasedVersionsById(String id); @@ -23,8 +24,6 @@ public interface CustomProductRepository { List getAllProductsWithIdAndReleaseTagAndArtifact(); - ProductModuleContent findByProductIdAndTagOrMavenVersion(String productId, String tag); - Page searchByCriteria(ProductSearchCriteria criteria, Pageable pageable); Product findByCriteria(ProductSearchCriteria criteria); diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/ImageRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ImageRepository.java index 7457d3296..48bb0cb31 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/ImageRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ImageRepository.java @@ -8,7 +8,7 @@ @Repository public interface ImageRepository extends MongoRepository { - Image findByProductIdAndSha(String productId, String sha); + List findByProductIdAndSha(String productId, String sha); List findByImageUrlEndsWithIgnoreCase(String fileName); 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 760f0407e..66f0ebea2 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 @@ -7,7 +7,7 @@ @Repository public interface ProductModuleContentRepository extends MongoRepository, CustomProductModuleContentRepository { - ProductModuleContent findByTagAndProductId(String tag, String productId); + ProductModuleContent findByVersionAndProductId(String version, String productId); void deleteAllByProductId(String productId); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductModuleContentRepositoryImpl.java b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductModuleContentRepositoryImpl.java index 933b6a366..5693609d9 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductModuleContentRepositoryImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductModuleContentRepositoryImpl.java @@ -21,10 +21,10 @@ public CustomProductModuleContentRepositoryImpl(MongoTemplate mongoTemplate) { } @Override - public List findTagsByProductId(String id) { + public List findVersionsByProductId(String id) { Aggregation aggregation = Aggregation.newAggregation(createFieldMatchOperation(MongoDBConstants.PRODUCT_ID, id), - createProjectAggregationBySingleFieldName(MongoDBConstants.TAG)); - return queryProductModuleContentsByAggregation(aggregation).stream().map(ProductModuleContent::getTag).toList(); + createProjectAggregationBySingleFieldName(MongoDBConstants.VERSION)); + return queryProductModuleContentsByAggregation(aggregation).stream().map(ProductModuleContent::getVersion).toList(); } public List queryProductModuleContentsByAggregation(Aggregation aggregation) { 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 672ba7323..aaeb2ba5d 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 @@ -11,6 +11,7 @@ import com.axonivy.market.enums.TypeOption; import com.axonivy.market.repository.CustomProductRepository; import com.axonivy.market.repository.CustomRepository; +import com.axonivy.market.repository.ProductJsonContentRepository; import com.axonivy.market.repository.ProductModuleContentRepository; import lombok.AllArgsConstructor; import lombok.Builder; @@ -28,7 +29,11 @@ import org.springframework.data.mongodb.core.query.Update; import org.springframework.util.CollectionUtils; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import static com.axonivy.market.enums.DocumentField.LISTED; import static com.axonivy.market.enums.DocumentField.TYPE; @@ -40,7 +45,8 @@ public class CustomProductRepositoryImpl extends CustomRepository implements Cus public static final String LOCALIZE_SEARCH_PATTERN = "%s.%s"; final MongoTemplate mongoTemplate; - final ProductModuleContentRepository contentRepo; + final ProductModuleContentRepository contentRepository; + final ProductJsonContentRepository jsonContentRepository; public Product queryProductByAggregation(Aggregation aggregation) { return Optional.of(mongoTemplate.aggregate(aggregation, EntityConstants.PRODUCT, Product.class)) @@ -53,36 +59,26 @@ public List queryProductsByAggregation(Aggregation aggregation) { } @Override - public Product getProductByIdWithTagOrVersion(String id, String tag) { + public Product getProductByIdAndVersion(String id, String version) { Product result = findProductById(id); if (!Objects.isNull(result)) { - ProductModuleContent content = findByProductIdAndTagOrMavenVersion(id, tag); + ProductModuleContent content = contentRepository.findByVersionAndProductId(version, id); result.setProductModuleContent(content); } return result; } @Override - public ProductModuleContent findByProductIdAndTagOrMavenVersion(String productId, String tag) { - Criteria productIdCriteria = Criteria.where(MongoDBConstants.PRODUCT_ID).is(productId); - Criteria orCriteria = new Criteria().orOperator( - Criteria.where(MongoDBConstants.TAG).is(tag), - Criteria.where(MongoDBConstants.MAVEN_VERSIONS).in(tag) - ); - Query query = new Query(new Criteria().andOperator(productIdCriteria, orCriteria)); - return mongoTemplate.findOne(query, ProductModuleContent.class); - } - - private Product findProductById(String id) { + public Product findProductById(String id) { Aggregation aggregation = Aggregation.newAggregation(createIdMatchOperation(id)); return queryProductByAggregation(aggregation); } @Override - public Product getProductById(String id) { + public Product getProductWithModuleContent(String id) { Product result = findProductById(id); if (!Objects.isNull(result)) { - ProductModuleContent content = contentRepo.findByTagAndProductId( + ProductModuleContent content = contentRepository.findByVersionAndProductId( result.getNewestReleaseVersion(), id); result.setProductModuleContent(content); } @@ -103,7 +99,7 @@ public int updateInitialCount(String productId, int initialCount) { 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); + return Optional.ofNullable(getProductWithModuleContent(productId)).map(Product::getInstallationCount).orElse(0); } @Override diff --git a/marketplace-service/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java b/marketplace-service/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java index f2643d28b..3a3d63bb2 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java +++ b/marketplace-service/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java @@ -1,8 +1,8 @@ package com.axonivy.market.schedulingtask; -import com.axonivy.market.service.MetadataService; import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.service.ExternalDocumentService; +import com.axonivy.market.service.MetadataService; import com.axonivy.market.service.ProductService; import lombok.AllArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -28,7 +28,7 @@ public class ScheduledTasks { @Scheduled(cron = SCHEDULING_TASK_PRODUCTS_CRON) public void syncDataForProductFromGitHubRepo() { log.warn("Started sync data for product from GitHub repo"); - productService.syncLatestDataFromMarketRepo(); + productService.syncLatestDataFromMarketRepo(false); } @Scheduled(cron = SCHEDULING_TASK_MAVEN_VERSION_CRON) diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/FileDownloadService.java b/marketplace-service/src/main/java/com/axonivy/market/service/FileDownloadService.java index ef986edd5..7f3544e83 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/FileDownloadService.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/FileDownloadService.java @@ -1,6 +1,6 @@ package com.axonivy.market.service; -import com.axonivy.market.entity.Metadata; +import com.axonivy.market.bo.Artifact; import java.io.File; import java.io.IOException; @@ -14,7 +14,7 @@ public interface FileDownloadService { byte[] downloadFile(String url); - String downloadAndUnzipProductContentFile(String url, Metadata snapShotMetadata) throws IOException; + String downloadAndUnzipProductContentFile(String url, Artifact artifact) throws IOException; void deleteDirectory(Path path); diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/ImageService.java b/marketplace-service/src/main/java/com/axonivy/market/service/ImageService.java index 90c8eee1b..ef9d7d05b 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/ImageService.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/ImageService.java @@ -9,7 +9,7 @@ public interface ImageService { Binary getImageBinary(GHContent ghContent); - Image mappingImageFromGHContent(String productId, GHContent ghContent, boolean isLogo); + Image mappingImageFromGHContent(String productId, GHContent ghContent); Image mappingImageFromDownloadedFolder(String productId, Path imagePath); diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/ProductContentService.java b/marketplace-service/src/main/java/com/axonivy/market/service/ProductContentService.java new file mode 100644 index 000000000..6e829e13b --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/service/ProductContentService.java @@ -0,0 +1,9 @@ +package com.axonivy.market.service; + +import com.axonivy.market.bo.Artifact; +import com.axonivy.market.entity.ProductModuleContent; + +public interface ProductContentService { + ProductModuleContent getReadmeAndProductContentsFromVersion(String productId, String version, String url, + Artifact artifact, String productName); +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/ProductJsonContentService.java b/marketplace-service/src/main/java/com/axonivy/market/service/ProductJsonContentService.java index a904c9173..4edfacacf 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/ProductJsonContentService.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/ProductJsonContentService.java @@ -1,8 +1,6 @@ package com.axonivy.market.service; -import com.axonivy.market.entity.Product; - public interface ProductJsonContentService { - void updateProductJsonContent(String jsonContent, String relatedTag, String currentVersion, String replaceVersion, - Product product); + void updateProductJsonContent(String jsonContent, String currentVersion, String replaceVersion, + String productId, String productName); } \ No newline at end of file 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 a1e6368bb..0c7321acb 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,15 +11,13 @@ public interface ProductService { Page findProducts(String type, String keyword, String language, Boolean isRESTClient, Pageable pageable); - List syncLatestDataFromMarketRepo(); + List syncLatestDataFromMarketRepo(Boolean resetSync); int updateInstallationCountForProduct(String key, String designerVersion); Product fetchProductDetail(String id, Boolean isShowDevVersion); - String getCompatibilityFromOldestTag(String oldestTag); - - void clearAllProducts(); + String getCompatibilityFromOldestVersion(String oldestVersion); void addCustomSortProduct(ProductCustomSortRequest customSort) throws InvalidParamException; diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/VersionService.java b/marketplace-service/src/main/java/com/axonivy/market/service/VersionService.java index 7449004a5..b339b3da0 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/VersionService.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/VersionService.java @@ -11,7 +11,7 @@ public interface VersionService { List getArtifactsAndVersionToDisplay(String productId, Boolean isShowDevVersion, String designerVersion); - Map getProductJsonContentByIdAndTag(String name, String version); + Map getProductJsonContentByIdAndVersion(String name, String version); List getVersionsForDesigner(String productId); diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/FileDownloadServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/FileDownloadServiceImpl.java index b811d6bed..78072bb28 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/FileDownloadServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/FileDownloadServiceImpl.java @@ -1,6 +1,6 @@ package com.axonivy.market.service.impl; -import com.axonivy.market.entity.Metadata; +import com.axonivy.market.bo.Artifact; import com.axonivy.market.service.FileDownloadService; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; @@ -8,14 +8,27 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; -import java.io.*; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.List; +import java.util.Set; +import java.util.UUID; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -39,8 +52,8 @@ public byte[] downloadFile(String url) { } @Override - public String downloadAndUnzipProductContentFile(String url, Metadata metadata) throws IOException { - String unzippedFilePath = String.join(File.separator, ROOT_STORAGE_FOR_PRODUCT_CONTENT, metadata.getArtifactId()); + public String downloadAndUnzipProductContentFile(String url, Artifact artifact) throws IOException { + String unzippedFilePath = String.join(File.separator, ROOT_STORAGE_FOR_PRODUCT_CONTENT, artifact.getArtifactId()); createFolder(unzippedFilePath); Path tempZipPath = createTempFileFromUrlAndExtractToLocation(url, unzippedFilePath, true); diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ImageServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ImageServiceImpl.java index ab700efa0..19d9de3ae 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ImageServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ImageServiceImpl.java @@ -13,6 +13,7 @@ import org.bson.types.Binary; import org.kohsuke.github.GHContent; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import java.io.IOException; import java.io.InputStream; @@ -52,17 +53,19 @@ private Binary getImageByDownloadUrl(String downloadUrl) { } @Override - public Image mappingImageFromGHContent(String productId, GHContent ghContent, boolean isLogo) { + public Image mappingImageFromGHContent(String productId, GHContent ghContent) { if (ObjectUtils.isEmpty(ghContent)) { log.info("There is missing for image content for product {}", productId); return null; } - if (!isLogo) { - Image existsImage = imageRepository.findByProductIdAndSha(productId, ghContent.getSha()); - if (ObjectUtils.isNotEmpty(existsImage)) { - return existsImage; + List existedImages = imageRepository.findByProductIdAndSha(productId, ghContent.getSha()); + if (!CollectionUtils.isEmpty(existedImages)) { + if (existedImages.size() > 1) { + List imagesToDelete = existedImages.subList(1, existedImages.size()); + imageRepository.deleteAll(imagesToDelete); } + return existedImages.get(0); } String currentImageUrl = GitHubUtils.getDownloadUrl(ghContent); diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/MetadataServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/MetadataServiceImpl.java index 9b7508531..a75585d32 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/MetadataServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/MetadataServiceImpl.java @@ -1,38 +1,33 @@ package com.axonivy.market.service.impl; import com.axonivy.market.bo.Artifact; -import com.axonivy.market.constants.CommonConstants; -import com.axonivy.market.constants.MavenConstants; -import com.axonivy.market.constants.ProductJsonConstants; -import com.axonivy.market.constants.ReadmeConstants; -import com.axonivy.market.entity.*; +import com.axonivy.market.entity.MavenArtifactVersion; +import com.axonivy.market.entity.Metadata; +import com.axonivy.market.entity.MetadataSync; +import com.axonivy.market.entity.Product; +import com.axonivy.market.entity.ProductJsonContent; import com.axonivy.market.model.MavenArtifactModel; -import com.axonivy.market.repository.*; -import com.axonivy.market.service.FileDownloadService; -import com.axonivy.market.service.ImageService; +import com.axonivy.market.repository.MavenArtifactVersionRepository; +import com.axonivy.market.repository.MetadataRepository; +import com.axonivy.market.repository.MetadataSyncRepository; +import com.axonivy.market.repository.ProductJsonContentRepository; +import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.service.MetadataService; -import com.axonivy.market.service.ProductJsonContentService; import com.axonivy.market.util.MavenUtils; import com.axonivy.market.util.MetadataReaderUtils; -import com.axonivy.market.util.ProductContentUtils; import com.axonivy.market.util.VersionUtils; import lombok.AllArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.util.Strings; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; -import java.util.stream.Stream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; @Service @AllArgsConstructor @@ -43,10 +38,6 @@ public class MetadataServiceImpl implements MetadataService { private final ProductJsonContentRepository productJsonRepo; private final MavenArtifactVersionRepository mavenArtifactVersionRepo; private final MetadataRepository metadataRepo; - private final ImageService imageService; - private final ProductJsonContentService productJsonContentService; - private final FileDownloadService fileDownloadService; - private final ProductModuleContentRepository productContentRepo; public void updateMavenArtifactVersionCacheWithModel(MavenArtifactVersion artifactVersionCache, @@ -67,8 +58,7 @@ public void updateMavenArtifactVersionCacheWithModel(MavenArtifactVersion artifa } } - public void updateMavenArtifactVersionData(String productId, List releasedVersions, - Set metadataSet, MavenArtifactVersion artifactVersionCache) { + public void updateMavenArtifactVersionData(Set metadataSet, MavenArtifactVersion artifactVersionCache) { for (Metadata metadata : metadataSet) { String metadataContent = MavenUtils.getMetadataContentFromUrl(metadata.getUrl()); if (StringUtils.isBlank(metadataContent)) { @@ -76,7 +66,6 @@ public void updateMavenArtifactVersionData(String productId, List releas } Metadata metadataWithVersions = MetadataReaderUtils.updateMetadataFromMavenXML(metadataContent, metadata, false); updateMavenArtifactVersionFromMetadata(artifactVersionCache, metadataWithVersions); - updateContentsFromNonMatchVersions(productId, releasedVersions, metadataWithVersions); } } @@ -129,7 +118,7 @@ public boolean syncProductMetadata(Product product) { return false; } artifactVersionCache.setAdditionalArtifactsByVersion(new HashMap<>()); - updateMavenArtifactVersionData(productId, product.getReleasedVersions(), metadataSet, artifactVersionCache); + updateMavenArtifactVersionData(metadataSet, artifactVersionCache); // Persist changed metadataSyncRepo.save(syncCache); @@ -138,183 +127,6 @@ public boolean syncProductMetadata(Product product) { return true; } - public void updateContentsFromNonMatchVersions(String productId, List releasedVersions, - Metadata metadata) { - List productModuleContents = new ArrayList<>(); - Set nonMatchSnapshotVersions = getNonMatchSnapshotVersions(productId, releasedVersions, - metadata.getVersions()); - - for (String nonMatchSnapshotVersion : nonMatchSnapshotVersions) { - if (MavenUtils.isProductArtifactId(metadata.getArtifactId())) { - handleProductArtifact(metadata.getProductId(), nonMatchSnapshotVersion, metadata, productModuleContents); - } - } - if (ObjectUtils.isNotEmpty(productModuleContents)) { - productContentRepo.saveAll(productModuleContents); - } - } - - public void handleProductArtifact(String productId, String nonMatchSnapshotVersion, Metadata productArtifact, - List productModuleContents) { - Metadata snapShotMetadata = MavenUtils.buildSnapShotMetadataFromVersion(productArtifact, nonMatchSnapshotVersion); - MetadataReaderUtils.updateMetadataFromMavenXML( - MavenUtils.getMetadataContentFromUrl(snapShotMetadata.getUrl()), snapShotMetadata, true); - - String url = buildProductFolderDownloadUrl(snapShotMetadata, nonMatchSnapshotVersion); - - Product product = productRepo.findById(productId).orElse(null); - if (StringUtils.isBlank(url) || Objects.isNull(product)) { - return; - } - - try { - addProductContent(product, nonMatchSnapshotVersion, snapShotMetadata, url, productModuleContents); - } catch (Exception e) { - log.error("Cannot download and unzip file {}", e.getMessage()); - } - } - - public String buildProductFolderDownloadUrl(Metadata snapShotMetadata, String nonMatchSnapshotVersion) { - return MavenUtils.buildDownloadUrl( - snapShotMetadata.getArtifactId(), nonMatchSnapshotVersion, - MavenConstants.DEFAULT_PRODUCT_FOLDER_TYPE, - snapShotMetadata.getRepoUrl(), snapShotMetadata.getGroupId(), - snapShotMetadata.getSnapshotVersionValue()); - } - - private void addProductContent(Product product, String nonMatchSnapshotVersion, Metadata snapShotMetadata, String url, - List productModuleContents) { - ProductModuleContent productModuleContent = getReadmeAndProductContentsFromTag(product, nonMatchSnapshotVersion, - snapShotMetadata, url); - if (Objects.nonNull(productModuleContent)) { - productModuleContents.add(productModuleContent); - } - } - - public Set getNonMatchSnapshotVersions(String productId, List releasedVersions, - Set metaVersions) { - Set nonMatchSnapshotVersions = new HashSet<>(); - for (String metaVersion : metaVersions) { - String matchedVersion = VersionUtils.getMavenVersionMatchWithTag(releasedVersions, metaVersion); - - updateProductJsonAndReadmeContents(productId, metaVersion, matchedVersion); - if (matchedVersion == null && VersionUtils.isSnapshotVersion(metaVersion)) { - nonMatchSnapshotVersions.add(metaVersion); - } - } - return nonMatchSnapshotVersions; - } - - private void updateProductJsonAndReadmeContents(String productId, String metaVersion, String matchedVersion) { - if (StringUtils.isNotBlank(matchedVersion)) { - // Clone new record from matchVersion's values - productJsonRepo.findByProductIdAndVersion(productId, - matchedVersion).stream().findAny().ifPresent(json -> - productRepo.findById(productId).ifPresent(product -> - productJsonContentService.updateProductJsonContent(json.getContent(), null, metaVersion, - matchedVersion, product) - ) - ); - - // Note metaVersion that get matchTag's contents to display - ProductModuleContent moduleContent = - productContentRepo.findByTagAndProductId(VersionUtils.convertVersionToTag(productId, matchedVersion), - productId); - if (ObjectUtils.isEmpty(moduleContent)) { - return; - } - Set mavenVersions = Optional.ofNullable(moduleContent.getMavenVersions()).orElse(new HashSet<>()); - if (!mavenVersions.contains(metaVersion)) { - mavenVersions.add(metaVersion); - moduleContent.setMavenVersions(mavenVersions); - productContentRepo.save(moduleContent); - } - } - } - - private ProductModuleContent getReadmeAndProductContentsFromTag(Product product, String nonMatchSnapshotVersion, - Metadata snapShotMetadata, String url) { - ProductModuleContent productModuleContent = ProductContentUtils.initProductModuleContent(product.getId(), Strings.EMPTY, - Set.of(nonMatchSnapshotVersion)); - String unzippedFolderPath = Strings.EMPTY; - try { - unzippedFolderPath = fileDownloadService.downloadAndUnzipProductContentFile(url, snapShotMetadata); - updateDependencyContentsFromProductJson(productModuleContent, product, unzippedFolderPath); - extractReadMeFileFromContents(product.getId(), unzippedFolderPath, productModuleContent); - } catch (Exception e) { - log.error("Cannot get product.json content in {}", e.getMessage()); - return null; - } finally { - if (StringUtils.isNotBlank(unzippedFolderPath)) { - fileDownloadService.deleteDirectory(Path.of(unzippedFolderPath)); - } - } - return productModuleContent; - } - - private void updateDependencyContentsFromProductJson(ProductModuleContent productModuleContent, - Product product, String unzippedFolderPath) throws IOException { - List artifacts = MavenUtils.convertProductJsonToMavenProductInfo( - Paths.get(unzippedFolderPath)); - ProductContentUtils.updateProductModule(productModuleContent, artifacts); - String currentVersion = productModuleContent.getMavenVersions().stream().findAny().orElse(null); - Path productJsonPath = Paths.get(unzippedFolderPath, ProductJsonConstants.PRODUCT_JSON_FILE); - String content = extractProductJsonContent(productJsonPath); - productJsonContentService.updateProductJsonContent(content, null, currentVersion, - ProductJsonConstants.VERSION_VALUE, product); - } - - private void extractReadMeFileFromContents(String productId, String unzippedFolderPath, - ProductModuleContent productModuleContent) { - try { - List readmeFiles; - Map> moduleContents = new HashMap<>(); - try (Stream readmePathStream = Files.walk(Paths.get(unzippedFolderPath))) { - readmeFiles = readmePathStream.filter(Files::isRegularFile).filter( - path -> path.getFileName().toString().startsWith(ReadmeConstants.README_FILE_NAME)).toList(); - } - if (ObjectUtils.isNotEmpty(readmeFiles)) { - for (Path readmeFile : readmeFiles) { - String readmeContents = Files.readString(readmeFile); - if (ProductContentUtils.hasImageDirectives(readmeContents)) { - readmeContents = updateImagesWithDownloadUrl(productId, unzippedFolderPath, readmeContents); - } - ProductContentUtils.getExtractedPartsOfReadme(moduleContents, readmeContents, - readmeFile.getFileName().toString()); - } - ProductContentUtils.updateProductModuleTabContents(productModuleContent, moduleContents); - } - } catch (Exception e) { - log.error("Cannot get README file's content from folder {}: {}", unzippedFolderPath, e.getMessage()); - } - } - - private String updateImagesWithDownloadUrl(String productId, String unzippedFolderPath, - String readmeContents) throws IOException { - List allImagePaths; - Map imageUrls = new HashMap<>(); - try (Stream imagePathStream = Files.walk(Paths.get(unzippedFolderPath))) { - allImagePaths = imagePathStream.filter(Files::isRegularFile).filter( - path -> path.getFileName().toString().toLowerCase().matches(CommonConstants.IMAGE_EXTENSION)).toList(); - } - allImagePaths.forEach( - imagePath -> Optional.of(imageService.mappingImageFromDownloadedFolder(productId, imagePath)).ifPresent( - image -> imageUrls.put(imagePath.getFileName().toString(), - CommonConstants.IMAGE_ID_PREFIX.concat(image.getId())))); - - return ProductContentUtils.replaceImageDirWithImageCustomId(imageUrls, readmeContents); - } - - private String extractProductJsonContent(Path filePath) { - try { - InputStream contentStream = MavenUtils.extractedContentStream(filePath); - return IOUtils.toString(Objects.requireNonNull(contentStream), StandardCharsets.UTF_8); - } catch (Exception e) { - log.error("Cannot extract product.json file {}", e.getMessage()); - return null; - } - } - public void updateMavenArtifactVersionFromMetadata(MavenArtifactVersion artifactVersionCache, Metadata metadata) { // Skip to add new model for product artifact diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductContentServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductContentServiceImpl.java new file mode 100644 index 000000000..51061e6f8 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductContentServiceImpl.java @@ -0,0 +1,112 @@ +package com.axonivy.market.service.impl; + +import com.axonivy.market.bo.Artifact; +import com.axonivy.market.constants.CommonConstants; +import com.axonivy.market.constants.ProductJsonConstants; +import com.axonivy.market.constants.ReadmeConstants; +import com.axonivy.market.entity.ProductModuleContent; +import com.axonivy.market.service.FileDownloadService; +import com.axonivy.market.service.ImageService; +import com.axonivy.market.service.ProductContentService; +import com.axonivy.market.service.ProductJsonContentService; +import com.axonivy.market.util.MavenUtils; +import com.axonivy.market.util.ProductContentUtils; +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.util.Strings; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +@Log4j2 +@Service +@AllArgsConstructor +public class ProductContentServiceImpl implements ProductContentService { + private final FileDownloadService fileDownloadService; + private final ProductJsonContentService productJsonContentService; + private final ImageService imageService; + + @Override + public ProductModuleContent getReadmeAndProductContentsFromVersion(String productId, String version, String url, + Artifact artifact, String productName) { + ProductModuleContent productModuleContent = ProductContentUtils.initProductModuleContent(productId, + version); + String unzippedFolderPath = Strings.EMPTY; + try { + unzippedFolderPath = fileDownloadService.downloadAndUnzipProductContentFile(url, artifact); + updateDependencyContentsFromProductJson(productModuleContent, productId, unzippedFolderPath, productName); + extractReadMeFileFromContents(productId, unzippedFolderPath, productModuleContent); + } catch (Exception e) { + log.error("Cannot get product.json content in {}", e.getMessage()); + return null; + } finally { + if (StringUtils.isNotBlank(unzippedFolderPath)) { + fileDownloadService.deleteDirectory(Path.of(unzippedFolderPath)); + } + } + return productModuleContent; + } + + public void updateDependencyContentsFromProductJson(ProductModuleContent productModuleContent, + String productId, String unzippedFolderPath, String productName) throws IOException { + List artifacts = MavenUtils.convertProductJsonToMavenProductInfo( + Paths.get(unzippedFolderPath)); + ProductContentUtils.updateProductModule(productModuleContent, artifacts); + Path productJsonPath = Paths.get(unzippedFolderPath, ProductJsonConstants.PRODUCT_JSON_FILE); + String content = MavenUtils.extractProductJsonContent(productJsonPath); + productJsonContentService.updateProductJsonContent(content, productModuleContent.getVersion(), + ProductJsonConstants.VERSION_VALUE, productId, productName); + } + + private void extractReadMeFileFromContents(String productId, String unzippedFolderPath, + ProductModuleContent productModuleContent) { + try { + List readmeFiles; + Map> moduleContents = new HashMap<>(); + try (Stream readmePathStream = Files.walk(Paths.get(unzippedFolderPath))) { + readmeFiles = readmePathStream.filter(Files::isRegularFile).filter( + path -> path.getFileName().toString().startsWith(ReadmeConstants.README_FILE_NAME)).toList(); + } + if (ObjectUtils.isNotEmpty(readmeFiles)) { + for (Path readmeFile : readmeFiles) { + String readmeContents = Files.readString(readmeFile); + if (ProductContentUtils.hasImageDirectives(readmeContents)) { + readmeContents = updateImagesWithDownloadUrl(productId, unzippedFolderPath, readmeContents); + } + ProductContentUtils.getExtractedPartsOfReadme(moduleContents, readmeContents, + readmeFile.getFileName().toString()); + } + ProductContentUtils.updateProductModuleTabContents(productModuleContent, moduleContents); + } + } catch (Exception e) { + log.error("Cannot get README file's content from folder {}: {}", unzippedFolderPath, e.getMessage()); + } + } + + public String updateImagesWithDownloadUrl(String productId, String unzippedFolderPath, + String readmeContents) throws IOException { + List allImagePaths; + Map imageUrls = new HashMap<>(); + try (Stream imagePathStream = Files.walk(Paths.get(unzippedFolderPath))) { + allImagePaths = imagePathStream.filter(Files::isRegularFile).filter( + path -> path.getFileName().toString().toLowerCase().matches(CommonConstants.IMAGE_EXTENSION)).toList(); + } + for (Path imagePath : allImagePaths) { + Optional.of(imageService.mappingImageFromDownloadedFolder(productId, imagePath)).ifPresent( + image -> imageUrls.put(imagePath.getFileName().toString(), + CommonConstants.IMAGE_ID_PREFIX.concat(image.getId()))); + } + + return ProductContentUtils.replaceImageDirWithImageCustomId(imageUrls, readmeContents); + } +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductJsonContentServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductJsonContentServiceImpl.java index cd0d3ca25..6b0a2dc48 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductJsonContentServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductJsonContentServiceImpl.java @@ -1,6 +1,5 @@ package com.axonivy.market.service.impl; -import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductJsonContent; import com.axonivy.market.factory.ProductFactory; import com.axonivy.market.repository.ProductJsonContentRepository; @@ -9,23 +8,20 @@ import org.apache.commons.lang3.ObjectUtils; import org.springframework.stereotype.Service; -import static com.axonivy.market.constants.ProductJsonConstants.EN_LANGUAGE; - @Service @AllArgsConstructor public class ProductJsonContentServiceImpl implements ProductJsonContentService { private final ProductJsonContentRepository productJsonRepo; @Override - public void updateProductJsonContent(String jsonContent, String relatedTag, String currentVersion, - String replaceVersion, Product product) { + public void updateProductJsonContent(String jsonContent, String currentVersion, String replaceVersion, + String productId, String productName) { if (ObjectUtils.isNotEmpty(jsonContent)) { ProductJsonContent productJsonContent = new ProductJsonContent(); - productJsonContent.setRelatedTag(relatedTag); productJsonContent.setVersion(currentVersion); - productJsonContent.setProductId(product.getId()); + productJsonContent.setProductId(productId); ProductFactory.mappingIdForProductJsonContent(productJsonContent); - productJsonContent.setName(product.getNames().get(EN_LANGUAGE)); + productJsonContent.setName(productName); productJsonContent.setContent(jsonContent.replace(replaceVersion, currentVersion)); productJsonRepo.save(productJsonContent); } 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 7577fb4bb..30f07264e 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,8 +1,9 @@ package com.axonivy.market.service.impl; -import com.axonivy.market.comparator.MavenVersionComparator; +import com.axonivy.market.bo.Artifact; import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.constants.GitHubConstants; +import com.axonivy.market.constants.MavenConstants; import com.axonivy.market.constants.MetaConstants; import com.axonivy.market.constants.ProductJsonConstants; import com.axonivy.market.criteria.ProductSearchCriteria; @@ -10,6 +11,7 @@ import com.axonivy.market.entity.Image; import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductCustomSort; +import com.axonivy.market.entity.ProductJsonContent; import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.enums.ErrorCode; import com.axonivy.market.enums.FileType; @@ -35,8 +37,10 @@ import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.service.ImageService; import com.axonivy.market.service.MetadataService; +import com.axonivy.market.service.ProductContentService; import com.axonivy.market.service.ProductService; import com.axonivy.market.util.MavenUtils; +import com.axonivy.market.util.MetadataReaderUtils; import com.axonivy.market.util.VersionUtils; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -48,7 +52,6 @@ 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; @@ -60,12 +63,17 @@ import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; import java.io.IOException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.security.SecureRandom; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -78,6 +86,8 @@ import java.util.function.Predicate; import static com.axonivy.market.constants.CommonConstants.SLASH; +import static com.axonivy.market.constants.MavenConstants.*; +import static com.axonivy.market.constants.ProductJsonConstants.EN_LANGUAGE; import static com.axonivy.market.constants.ProductJsonConstants.LOGO_FILE; import static com.axonivy.market.enums.DocumentField.MARKET_DIRECTORY; import static com.axonivy.market.enums.DocumentField.SHORT_DESCRIPTIONS; @@ -105,6 +115,7 @@ public class ProductServiceImpl implements ProductService { private final ImageService imageService; private final MongoTemplate mongoTemplate; private final MetadataService metadataService; + private final ProductContentService productContentService; private final ObjectMapper mapper = new ObjectMapper(); private final SecureRandom random = new SecureRandom(); private GHCommit lastGHCommit; @@ -119,9 +130,9 @@ public ProductServiceImpl(ProductRepository productRepo, GHAxonIvyMarketRepoService axonIvyMarketRepoService, GHAxonIvyProductRepoService axonIvyProductRepoService, GitHubRepoMetaRepository gitHubRepoMetaRepo, GitHubService gitHubService, ProductCustomSortRepository productCustomSortRepo, MavenArtifactVersionRepository mavenArtifactVersionRepo, - ImageRepository imageRepo, MetadataService metadataService, MetadataSyncRepository metadataSyncRepo, - MetadataRepository metadataRepo, ImageService imageService, MongoTemplate mongoTemplate, - ProductJsonContentRepository productJsonContentRepo) { + ProductJsonContentRepository productJsonContentRepo, ImageRepository imageRepo, MetadataService metadataService, + MetadataSyncRepository metadataSyncRepo, MetadataRepository metadataRepo, ImageService imageService, + MongoTemplate mongoTemplate, ProductContentService productContentService) { this.productRepo = productRepo; this.productModuleContentRepo = productModuleContentRepo; this.axonIvyMarketRepoService = axonIvyMarketRepoService; @@ -130,17 +141,14 @@ public ProductServiceImpl(ProductRepository productRepo, this.gitHubService = gitHubService; this.productCustomSortRepo = productCustomSortRepo; this.mavenArtifactVersionRepo = mavenArtifactVersionRepo; + this.productJsonContentRepo = productJsonContentRepo; this.metadataSyncRepo = metadataSyncRepo; this.metadataRepo = metadataRepo; this.metadataService = metadataService; this.imageRepo = imageRepo; this.imageService = imageService; this.mongoTemplate = mongoTemplate; - this.productJsonContentRepo = productJsonContentRepo; - } - - private static Predicate filterNonPersistGhTagName(List currentTags) { - return tag -> !currentTags.contains(tag.getName()); + this.productContentService = productContentService; } @Override @@ -160,25 +168,33 @@ public Page findProducts(String type, String keyword, String language, } @Override - public List syncLatestDataFromMarketRepo() { + public List syncLatestDataFromMarketRepo(Boolean resetSync) { List syncedProductIds = new ArrayList<>(); - var isAlreadyUpToDate = isLastGithubCommitCovered(); + var isAlreadyUpToDate = false; + marketRepoMeta = gitHubRepoMetaRepo.findByRepoName(GitHubConstants.AXONIVY_MARKETPLACE_REPO_NAME); + if (BooleanUtils.isTrue(resetSync) && marketRepoMeta != null) { + gitHubRepoMetaRepo.delete(marketRepoMeta); + marketRepoMeta = null; + } else { + isAlreadyUpToDate = isLastGithubCommitCovered(); + } + if (!isAlreadyUpToDate) { if (marketRepoMeta == null) { - syncedProductIds = syncProductsFromGitHubRepo(); + syncedProductIds = syncProductsFromGitHubRepo(resetSync); marketRepoMeta = new GitHubRepoMeta(); } else { syncedProductIds = updateLatestChangeToProductsFromGithubRepo(); } syncRepoMetaDataStatus(); } - updateLatestReleaseTagContentsFromProductRepo(); + updateLatestReleaseVersionContentsFromProductRepo(); return syncedProductIds.stream().filter(StringUtils::isNotBlank).toList(); } @Override public int updateInstallationCountForProduct(String key, String designerVersion) { - Product product = productRepo.getProductById(key); + Product product = productRepo.getProductWithModuleContent(key); if (Objects.isNull(product)) { return 0; } @@ -313,7 +329,7 @@ private String modifyProductLogo(String parentPath, GHContent fileContent) { searchCriteria.setFields(List.of(MARKET_DIRECTORY)); Product result = productRepo.findByCriteria(searchCriteria); if (result != null) { - Optional.ofNullable(imageService.mappingImageFromGHContent(result.getId(), fileContent, true)).ifPresent(image -> { + Optional.ofNullable(imageService.mappingImageFromGHContent(result.getId(), fileContent)).ifPresent(image -> { if (StringUtils.isNotBlank(result.getLogoId())) { imageRepo.deleteById(result.getLogoId()); } @@ -362,7 +378,6 @@ private Order getExtensionOrder(String language) { private boolean isLastGithubCommitCovered() { boolean isLastCommitCovered = false; long lastCommitTime = 0L; - marketRepoMeta = gitHubRepoMetaRepo.findByRepoName(GitHubConstants.AXONIVY_MARKETPLACE_REPO_NAME); if (marketRepoMeta != null) { lastCommitTime = marketRepoMeta.getLastChange(); } @@ -374,30 +389,19 @@ private boolean isLastGithubCommitCovered() { return isLastCommitCovered; } - private void updateLatestReleaseTagContentsFromProductRepo() { + private void updateLatestReleaseVersionContentsFromProductRepo() { List products = productRepo.findAll(); if (ObjectUtils.isEmpty(products)) { return; } for (Product product : products) { - if (StringUtils.isNotBlank(product.getRepositoryName())) { - getProductContents(product); - productRepo.save(product); - } - } - } - - private void getProductContents(Product product) { - try { - GHRepository productRepository = gitHubService.getRepository(product.getRepositoryName()); - updateProductFromReleaseTags(product, productRepository); - } catch (IOException e) { - log.error("Cannot find product repository {} {}", product.getRepositoryName(), e); + updateProductFromReleasedVersions(product); + productRepo.save(product); } } - private List syncProductsFromGitHubRepo() { + private List syncProductsFromGitHubRepo(Boolean resetSync) { log.warn("**ProductService: synchronize products from scratch based on the Market repo"); List syncedProductIds = new ArrayList<>(); var gitHubContentMap = axonIvyMarketRepoService.fetchAllMarketItems(); @@ -411,10 +415,16 @@ private List syncProductsFromGitHubRepo() { mappingVendorImageFromGHContent(product, content); mappingLogoFromGHContent(product, content); } - if (productRepo.findById(product.getId()).isPresent()) { + + if (BooleanUtils.isTrue(resetSync)) { + productModuleContentRepo.deleteAllByProductId(product.getId()); + productJsonContentRepo.deleteAllByProductId(product.getId()); + } else if (productRepo.findById(product.getId()).isPresent()) { continue; } - updateRelatedThingsOfProductFromGHContent(ghContentEntity.getValue(), product); + + updateProductContentForNonStandardProduct(ghContentEntity.getValue(), product); + updateProductFromReleasedVersions(product); transferComputedDataFromDB(product); syncedProductIds.add(productRepo.save(product).getId()); } @@ -423,7 +433,7 @@ private List syncProductsFromGitHubRepo() { private void mappingLogoFromGHContent(Product product, GHContent ghContent) { if (ghContent != null && StringUtils.endsWith(ghContent.getName(), LOGO_FILE)) { - Optional.ofNullable(imageService.mappingImageFromGHContent(product.getId(), ghContent, true)) + Optional.ofNullable(imageService.mappingImageFromGHContent(product.getId(), ghContent)) .ifPresent(image -> product.setLogoId(image.getId())); } } @@ -444,7 +454,7 @@ private String mapVendorImage(String productId, GHContent ghContent, String imag String imagePath = StringUtils.replace(ghContent.getPath(), MetaConstants.META_FILE, imageName); try { GHContent imageContent = gitHubService.getGHContent(ghContent.getOwner(), imagePath, marketRepoBranch); - return Optional.ofNullable(imageService.mappingImageFromGHContent(productId, imageContent, false)) + return Optional.ofNullable(imageService.mappingImageFromGHContent(productId, imageContent)) .map(Image::getId).orElse(EMPTY); } catch (IOException e) { log.error("Get Vendor Image failed: ", e); @@ -453,82 +463,154 @@ private String mapVendorImage(String productId, GHContent ghContent, String imag return EMPTY; } - private void updateProductFromReleaseTags(Product product, GHRepository productRepo) { - List productModuleContents = new ArrayList<>(); - List ghTags = getProductReleaseTags(product); - GHTag lastTag = MavenVersionComparator.findHighestTag(ghTags); - if (lastTag == null || lastTag.getName().equals(product.getNewestReleaseVersion())) { + private void updateProductFromReleasedVersions(Product product) { + if (ObjectUtils.isEmpty(product.getArtifacts())) { return; } - product.setNewestPublishedDate(getPublishedDateFromLatestTag(lastTag)); - product.setNewestReleaseVersion(lastTag.getName()); - List currentTags = VersionUtils.getReleaseTagsFromProduct(product); - if (CollectionUtils.isEmpty(currentTags)) { - currentTags = productModuleContentRepo.findTagsByProductId(product.getId()); + + List productArtifacts = product.getArtifacts().stream() + .filter(productArtifact -> productArtifact.getArtifactId().contains(MavenConstants.PRODUCT_ARTIFACT_POSTFIX)) + .toList(); + + List archivedArtifacts = product.getArtifacts().stream() + .filter(artifact -> !CollectionUtils.isEmpty(artifact.getArchivedArtifacts())) + .flatMap(artifact -> artifact.getArchivedArtifacts().stream() + .map(archivedArtifact -> Artifact.builder() + .groupId(archivedArtifact.getGroupId()) + .artifactId(archivedArtifact.getArtifactId()) + .build())) + .toList(); + + List mavenArtifacts = new ArrayList<>(); + mavenArtifacts.addAll(productArtifacts); + mavenArtifacts.addAll(archivedArtifacts); + + for (Artifact mavenArtifact : mavenArtifacts) { + getMetadataContent(mavenArtifact, product); } - ghTags = ghTags.stream().filter(filterNonPersistGhTagName(currentTags)).toList(); + } - for (GHTag ghTag : ghTags) { - ProductModuleContent productModuleContent = - axonIvyProductRepoService.getReadmeAndProductContentsFromTag(product, productRepo, ghTag.getName()); - if (productModuleContent != null) { - productModuleContents.add(productModuleContent); - } - String versionFromTag = VersionUtils.convertTagToVersion(ghTag.getName()); - if (Objects.isNull(product.getReleasedVersions())) { - product.setReleasedVersions(new ArrayList<>()); - } - product.getReleasedVersions().add(versionFromTag); + private void getMetadataContent(Artifact artifact, Product product) { + String metadataUrl = MavenUtils.buildMetadataUrlFromArtifactInfo(artifact.getRepoUrl(), artifact.getGroupId(), + createProductArtifactId(artifact)); + String metadataContent = MavenUtils.getMetadataContentFromUrl(metadataUrl); + if (StringUtils.isNotBlank(metadataContent)) { + updateContentsFromMavenXML(product, metadataContent, artifact); + } + } + + private void updateContentsFromMavenXML(Product product, String metadataContent, Artifact mavenArtifact) { + Document document = MetadataReaderUtils.getDocumentFromXMLContent(metadataContent); + + String latestVersion = MetadataReaderUtils.getElementValue(document, MavenConstants.LATEST_VERSION_TAG); + if (StringUtils.equals(latestVersion, product.getNewestReleaseVersion())) { + return; + } + product.setNewestPublishedDate(getNewestPublishedDate(document)); + product.setNewestReleaseVersion(latestVersion); + + NodeList versionNodes = document.getElementsByTagName(MavenConstants.VERSION_TAG); + List mavenVersions = new ArrayList<>(); + for (int i = 0; i < versionNodes.getLength(); i++) { + mavenVersions.add(versionNodes.item(i).getTextContent()); + } + + updateProductCompatibility(product, mavenVersions); + + List currentVersions = product.getReleasedVersions(); + if (CollectionUtils.isEmpty(currentVersions)) { + product.setReleasedVersions(new ArrayList<>()); + currentVersions = productModuleContentRepo.findVersionsByProductId(product.getId()); } - if (!CollectionUtils.isEmpty(productModuleContents)) { + mavenVersions = mavenVersions.stream().filter(filterNonPersistVersion(currentVersions)).toList(); + + List productModuleContents = new ArrayList<>(); + for (String version : mavenVersions) { + product.getReleasedVersions().add(version); + handleProductArtifact(version, product.getId(), productModuleContents, mavenArtifact, + product.getNames().get(EN_LANGUAGE)); + } + + if (ObjectUtils.isNotEmpty(productModuleContents)) { productModuleContentRepo.saveAll(productModuleContents); } } - private Date getPublishedDateFromLatestTag(GHTag lastTag) { - try { - return lastTag.getCommit().getCommitDate(); - } catch (Exception e) { - log.error("Fail to get commit date ", e); + private Date getNewestPublishedDate(Document document) { + DateTimeFormatter lastUpdatedFormatter = DateTimeFormatter.ofPattern(MavenConstants.DATE_TIME_FORMAT); + LocalDateTime newestPublishedDate = + LocalDateTime.parse(Objects.requireNonNull(MetadataReaderUtils.getElementValue(document, + MavenConstants.LAST_UPDATED_TAG)), lastUpdatedFormatter); + return Date.from(newestPublishedDate.atZone(ZoneOffset.UTC).toInstant()); + } + + private void updateProductCompatibility(Product product, List mavenVersions) { + if (StringUtils.isBlank(product.getCompatibility())) { + String oldestVersion = VersionUtils.getOldestVersions(mavenVersions); + if (oldestVersion != null) { + String compatibility = getCompatibilityFromOldestVersion(oldestVersion); + product.setCompatibility(compatibility); + } } - return null; } - private void updateProductCompatibility(Product product) { - if (StringUtils.isNotBlank(product.getCompatibility())) { + private static Predicate filterNonPersistVersion(List currentVersions) { + return version -> !currentVersions.contains(version); + } + + public void handleProductArtifact(String version, String productId, + List productModuleContents, Artifact mavenArtifact, String productName) { + String snapshotVersionValue = Strings.EMPTY; + if (version.contains(MavenConstants.SNAPSHOT_VERSION)) { + snapshotVersionValue = MetadataReaderUtils.getSnapshotVersionValue(version, mavenArtifact); + } + + String repoUrl = StringUtils.defaultIfBlank(mavenArtifact.getRepoUrl(), DEFAULT_IVY_MAVEN_BASE_URL); + String artifactId = createProductArtifactId(mavenArtifact); + String type = StringUtils.defaultIfBlank(mavenArtifact.getType(), DEFAULT_PRODUCT_FOLDER_TYPE); + String url = MavenUtils.buildDownloadUrl(artifactId, version, type, + repoUrl, mavenArtifact.getGroupId(), StringUtils.defaultIfBlank(snapshotVersionValue, version)); + + if (StringUtils.isBlank(url)) { return; } - String oldestVersion = VersionUtils.getOldestVersion(getProductReleaseTags(product)); - if (oldestVersion != null) { - String compatibility = getCompatibilityFromOldestTag(oldestVersion); - product.setCompatibility(compatibility); + + try { + addProductContent(productId, version, url, productModuleContents, mavenArtifact, productName); + } catch (Exception e) { + log.error("Cannot download and unzip file {}", e.getMessage()); } } - private List getProductReleaseTags(Product product) { - try { - return gitHubService.getRepositoryTags(product.getRepositoryName()); - } catch (IOException e) { - log.error("Cannot get tag list of product ", e); + private String createProductArtifactId(Artifact mavenArtifact) { + return mavenArtifact.getArtifactId().contains(PRODUCT_ARTIFACT_POSTFIX) ? mavenArtifact.getArtifactId() + : mavenArtifact.getArtifactId().concat(PRODUCT_ARTIFACT_POSTFIX); + } + + public void addProductContent(String productId, String version, String url, + List productModuleContents, Artifact artifact, String productName) { + ProductModuleContent productModuleContent = productContentService.getReadmeAndProductContentsFromVersion(productId, + version, url, artifact, productName); + if (Objects.nonNull(productModuleContent)) { + productModuleContents.add(productModuleContent); } - return List.of(); } // Cover 3 cases after removing non-numeric characters (8, 11.1 and 10.0.2) @Override - public String getCompatibilityFromOldestTag(String oldestTag) { - if (StringUtils.isBlank(oldestTag)) { + public String getCompatibilityFromOldestVersion(String oldestVersion) { + if (StringUtils.isBlank(oldestVersion)) { return Strings.EMPTY; } - if (!oldestTag.contains(CommonConstants.DOT_SEPARATOR)) { - return oldestTag + ".0+"; + if (!oldestVersion.contains(CommonConstants.DOT_SEPARATOR)) { + return oldestVersion + ".0+"; } - int firstDot = oldestTag.indexOf(CommonConstants.DOT_SEPARATOR); - int secondDot = oldestTag.indexOf(CommonConstants.DOT_SEPARATOR, firstDot + 1); + int firstDot = oldestVersion.indexOf(CommonConstants.DOT_SEPARATOR); + int secondDot = oldestVersion.indexOf(CommonConstants.DOT_SEPARATOR, firstDot + 1); if (secondDot == -1) { - return oldestTag.concat(CommonConstants.PLUS); + return oldestVersion.concat(CommonConstants.PLUS); } - return oldestTag.substring(0, secondDot).concat(CommonConstants.PLUS); + return oldestVersion.substring(0, secondDot).concat(CommonConstants.PLUS); } @Override @@ -545,10 +627,9 @@ public Product fetchBestMatchProductDetail(String id, String version) { List installableVersions = VersionUtils.getInstallableVersionsFromMetadataList( metadataRepo.findByProductId(id)); String bestMatchVersion = VersionUtils.getBestMatchVersion(installableVersions, version); - String bestMatchTag = VersionUtils.convertVersionToTag(id, bestMatchVersion); - // Cover exception case of employee onboarding without any product.json file - Product product = StringUtils.isBlank(bestMatchTag) ? getProductByIdWithNewestReleaseVersion(id, - false) : productRepo.getProductByIdWithTagOrVersion(id, bestMatchTag); + // Cover exception case of employee onboarding without any product.json file + Product product = StringUtils.isBlank(bestMatchVersion) ? getProductByIdWithNewestReleaseVersion(id, + false) : productRepo.getProductByIdAndVersion(id, bestMatchVersion); return Optional.ofNullable(product).map(productItem -> { updateProductInstallationCount(id, productItem); productItem.setBestMatchVersion(bestMatchVersion); @@ -559,19 +640,26 @@ public Product fetchBestMatchProductDetail(String id, String version) { public Product getProductByIdWithNewestReleaseVersion(String id, Boolean isShowDevVersion) { List versions; String version = StringUtils.EMPTY; + var mavenArtifactVersion = mavenArtifactVersionRepo.findById(id); - if(mavenArtifactVersion.isPresent()) { + if (mavenArtifactVersion.isPresent()) { versions = MavenUtils.getAllExistingVersions(mavenArtifactVersion.get(), BooleanUtils.isTrue(isShowDevVersion), StringUtils.EMPTY); - version = VersionUtils.convertVersionToTag(id, CollectionUtils.firstElement(versions)); + version = CollectionUtils.firstElement(versions); } + // Cover exception case of employee onboarding without any product.json file if (StringUtils.isBlank(version)) { versions = VersionUtils.getVersionsToDisplay(productRepo.getReleasedVersionsById(id), isShowDevVersion, StringUtils.EMPTY); version = CollectionUtils.firstElement(versions); } - return productRepo.getProductByIdWithTagOrVersion(id, version); + + Product product = productRepo.getProductByIdAndVersion(id, version); + productJsonContentRepo.findByProductIdAndVersion(id, version).stream().map( + ProductJsonContent::getContent).findFirst().ifPresent( + jsonContent -> product.setMavenDropins(MavenUtils.isJsonContentContainOnlyMavenDropins(jsonContent))); + return product; } public void updateProductInstallationCount(String id, Product productItem) { @@ -584,13 +672,7 @@ public void updateProductInstallationCount(String id, Product productItem) { @Override public Product fetchProductDetailByIdAndVersion(String id, String version) { - return productRepo.getProductByIdWithTagOrVersion(id, version); - } - - @Override - public void clearAllProducts() { - gitHubRepoMetaRepo.deleteAll(); - productRepo.deleteAll(); + return productRepo.getProductByIdAndVersion(id, version); } @Override @@ -646,7 +728,8 @@ public boolean syncOneProduct(String productId, String marketItemPath, Boolean o if (!CollectionUtils.isEmpty(gitHubContents)) { log.info("Update data of product {} from meta.json and logo files", productId); mappingMetaDataAndLogoFromGHContent(gitHubContents, product); - updateRelatedThingsOfProductFromGHContent(gitHubContents, product); + updateProductContentForNonStandardProduct(gitHubContents, product); + updateProductFromReleasedVersions(product); productRepo.save(product); metadataService.syncProductMetadata(product); log.info("Sync product {} is finished!", productId); @@ -697,24 +780,17 @@ private void mappingMetaDataAndLogoFromGHContent(List gitHubContent, } } - private void updateRelatedThingsOfProductFromGHContent(List gitHubContents, Product product) { - if (StringUtils.isNotBlank(product.getRepositoryName())) { - updateProductCompatibility(product); - getProductContents(product); - } else { - updateProductContentForNonStandardProduct(gitHubContents, product); - } - } - private void updateProductContentForNonStandardProduct(List ghContentEntity, Product product) { - ProductModuleContent initialContent = new ProductModuleContent(); - initialContent.setTag(INITIAL_VERSION); - initialContent.setProductId(product.getId()); - ProductFactory.mappingIdForProductModuleContent(initialContent); - product.setReleasedVersions(List.of(INITIAL_VERSION)); - product.setNewestReleaseVersion(INITIAL_VERSION); - axonIvyProductRepoService.extractReadMeFileFromContents(product, ghContentEntity, initialContent); - productModuleContentRepo.save(initialContent); + if (StringUtils.isBlank(product.getRepositoryName())) { + ProductModuleContent initialContent = new ProductModuleContent(); + initialContent.setVersion(INITIAL_VERSION); + initialContent.setProductId(product.getId()); + ProductFactory.mappingIdForProductModuleContent(initialContent); + product.setReleasedVersions(List.of(INITIAL_VERSION)); + product.setNewestReleaseVersion(INITIAL_VERSION); + axonIvyProductRepoService.extractReadMeFileFromContents(product, ghContentEntity, initialContent); + productModuleContentRepo.save(initialContent); + } } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java index d6bbe394a..d1fe5af92 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java @@ -34,7 +34,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import static com.axonivy.market.constants.ProductJsonConstants.NAME; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; @@ -76,11 +75,11 @@ public List getArtifactsAndVersionToDisplay(String pr return results; } - public Map getProductJsonContentByIdAndTag(String productId, String tag) { + public Map getProductJsonContentByIdAndVersion(String productId, String version) { Map result = new HashMap<>(); try { ProductJsonContent productJsonContent = - productJsonRepo.findByProductIdAndVersion(productId, tag).stream().findAny().orElse(null); + productJsonRepo.findByProductIdAndVersion(productId, version).stream().findAny().orElse(null); if (ObjectUtils.isEmpty(productJsonContent)) { return new HashMap<>(); } @@ -112,23 +111,10 @@ public List getVersionsForDesigner(String productId) { return versionAndUrlList; } - public List getPersistedVersions(String productId) { - var product = productRepo.findById(productId); - List versions = new ArrayList<>(); - if (product.isPresent()) { - versions.addAll(product.get().getReleasedVersions()); - } - if (CollectionUtils.isEmpty(versions)) { - versions.addAll(productContentRepo.findTagsByProductId(productId)); - versions = versions.stream().map(VersionUtils::convertTagToVersion).collect(Collectors.toList()); - } - return versions; - } - - public List getMavenArtifactsFromProductJsonByTag(String tag, + public List getMavenArtifactsFromProductJsonByVersion(String version, String productId) { ProductJsonContent productJson = - productJsonRepo.findByProductIdAndVersion(productId, tag).stream().findAny().orElse(null); + productJsonRepo.findByProductIdAndVersion(productId, version).stream().findAny().orElse(null); return MavenUtils.getMavenArtifactsFromProductJson(productJson); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/util/MavenUtils.java b/marketplace-service/src/main/java/com/axonivy/market/util/MavenUtils.java index 95037618f..2de5f1cf4 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/util/MavenUtils.java +++ b/marketplace-service/src/main/java/com/axonivy/market/util/MavenUtils.java @@ -144,6 +144,15 @@ public static List extractMavenArtifactsFromContentStream(InputStream return artifacts; } + public static String extractProductJsonContent(Path filePath) { + try (InputStream contentStream = extractedContentStream(filePath)) { + return IOUtils.toString(Objects.requireNonNull(contentStream), StandardCharsets.UTF_8); + } catch (Exception e) { + log.error("Cannot extract product.json file {}", filePath); + return null; + } + } + public static String buildDownloadUrl(Artifact artifact, String version) { String groupIdByVersion = artifact.getGroupId(); String artifactIdByVersion = artifact.getArtifactId(); @@ -160,13 +169,6 @@ public static String buildDownloadUrl(Artifact artifact, String version) { StringUtils.EMPTY); } - public static String buildDownloadUrl(Metadata metadata, String version) { - String groupIdByVersion = metadata.getGroupId(); - groupIdByVersion = groupIdByVersion.replace(CommonConstants.DOT_SEPARATOR, CommonConstants.SLASH); - return buildDownloadUrl(metadata.getArtifactId(), version, metadata.getType(), metadata.getRepoUrl(), - groupIdByVersion, StringUtils.EMPTY); - } - public static String buildDownloadUrl(String artifactId, String baseVersion, String type, String repoUrl, String groupId, String version) { groupId = groupId.replace(CommonConstants.DOT_SEPARATOR, CommonConstants.SLASH); @@ -177,7 +179,6 @@ public static String buildDownloadUrl(String artifactId, String baseVersion, Str return String.join(CommonConstants.SLASH, repoUrl, groupId, artifactId, baseVersion, artifactFileName); } - public static ArchivedArtifact findArchivedArtifactInfoBestMatchWithVersion(String version, List archivedArtifacts) { if (CollectionUtils.isEmpty(archivedArtifacts)) { @@ -324,4 +325,10 @@ public static boolean isProductMetadata(Metadata metadata) { return StringUtils.endsWith(Objects.requireNonNullElse(metadata, new Metadata()).getArtifactId(), MavenConstants.PRODUCT_ARTIFACT_POSTFIX); } + + public static boolean isJsonContentContainOnlyMavenDropins(String jsonContent) { + return jsonContent.contains(ProductJsonConstants.MAVEN_DROPINS_INSTALLER_ID) && !jsonContent.contains( + ProductJsonConstants.MAVEN_IMPORT_INSTALLER_ID) && !jsonContent.contains( + ProductJsonConstants.MAVEN_DEPENDENCY_INSTALLER_ID); + } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/util/MetadataReaderUtils.java b/marketplace-service/src/main/java/com/axonivy/market/util/MetadataReaderUtils.java index dfcbe0c5c..67c9a3893 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/util/MetadataReaderUtils.java +++ b/marketplace-service/src/main/java/com/axonivy/market/util/MetadataReaderUtils.java @@ -1,7 +1,10 @@ package com.axonivy.market.util; +import com.axonivy.market.bo.Artifact; import com.axonivy.market.constants.MavenConstants; import com.axonivy.market.entity.Metadata; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; import org.w3c.dom.Document; import org.w3c.dom.NodeList; @@ -15,19 +18,17 @@ import java.util.Objects; @Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class MetadataReaderUtils { private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern( MavenConstants.DATE_TIME_FORMAT); private static final DateTimeFormatter SNAPSHOT_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern( MavenConstants.SNAPSHOT_LAST_UPDATED_DATE_TIME_FORMAT); - private MetadataReaderUtils() { - } - public static Metadata updateMetadataFromMavenXML(String xmlData, Metadata metadata, boolean isSnapShot) { + public static Metadata updateMetadataFromMavenXML(String xmlData, Metadata metadata, + boolean isSnapShot) { + Document document = getDocumentFromXMLContent(xmlData); try { - DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - Document document = builder.parse(new InputSource(new StringReader(xmlData))); - document.getDocumentElement().normalize(); LocalDateTime lastUpdated = getLastUpdatedTimeFromDocument(document, isSnapShot); if (lastUpdated.equals(metadata.getLastUpdated())) { return metadata; @@ -35,7 +36,7 @@ public static Metadata updateMetadataFromMavenXML(String xmlData, Metadata metad metadata.setLastUpdated(lastUpdated); updateMetadataVersions(metadata, document, isSnapShot); } catch (Exception e) { - log.error("Metadata Reader: can not read the metadata of {} with error", xmlData, e); + log.error("Update metadata from maven failed {}", e.getMessage()); } return metadata; } @@ -62,11 +63,32 @@ private static LocalDateTime getLastUpdatedTimeFromDocument(Document document, b return LocalDateTime.parse(textValue, lastUpdatedFormatter); } - private static String getElementValue(Document doc, String tagName) { + public static String getElementValue(Document doc, String tagName) { NodeList nodeList = doc.getElementsByTagName(tagName); if (nodeList.getLength() > 0) { return nodeList.item(0).getTextContent(); } return null; } + + public static String getSnapshotVersionValue(String version, + Artifact mavenArtifact) { + String snapShotMetadataUrl = MavenUtils.buildSnapshotMetadataUrlFromArtifactInfo(mavenArtifact.getRepoUrl(), + mavenArtifact.getGroupId(), mavenArtifact.getArtifactId(), version); + String metadataContent = MavenUtils.getMetadataContentFromUrl(snapShotMetadataUrl); + Document document = getDocumentFromXMLContent(metadataContent); + return getElementValue(document, MavenConstants.VALUE_TAG); + } + + public static Document getDocumentFromXMLContent(String xmlData) { + Document document = null; + try { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + document = builder.parse(new InputSource(new StringReader(xmlData))); + document.getDocumentElement().normalize(); + } catch (Exception e) { + log.error("Metadata Reader: can not read the metadata of {} with error", xmlData, e); + } + return document; + } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/util/ProductContentUtils.java b/marketplace-service/src/main/java/com/axonivy/market/util/ProductContentUtils.java index bb0fe9f8e..9d63d6fe1 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/util/ProductContentUtils.java +++ b/marketplace-service/src/main/java/com/axonivy/market/util/ProductContentUtils.java @@ -14,10 +14,11 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static com.axonivy.market.constants.ProductJsonConstants.DEFAULT_PRODUCT_TYPE; + public class ProductContentUtils { public static final String DEMO_SETUP_TITLE = "(?i)## Demo|## Setup"; private static final String HASH = "#"; @@ -114,11 +115,10 @@ public static String removeFirstLine(String text) { return result; } - public static ProductModuleContent initProductModuleContent(String productId, String tag, Set mavenVersions) { + public static ProductModuleContent initProductModuleContent(String productId, String version) { ProductModuleContent productModuleContent = new ProductModuleContent(); productModuleContent.setProductId(productId); - productModuleContent.setTag(tag); - productModuleContent.setMavenVersions(mavenVersions); + productModuleContent.setVersion(version); ProductFactory.mappingIdForProductModuleContent(productModuleContent); return productModuleContent; } @@ -129,7 +129,7 @@ public static void updateProductModule(ProductModuleContent productModuleContent productModuleContent.setIsDependency(Boolean.TRUE); productModuleContent.setGroupId(artifact.getGroupId()); productModuleContent.setArtifactId(artifact.getArtifactId()); - productModuleContent.setType(artifact.getType()); + productModuleContent.setType(StringUtils.defaultIfBlank(artifact.getType(), DEFAULT_PRODUCT_TYPE)); productModuleContent.setName(artifact.getName()); } } 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 ab36f2185..ced7b9047 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 @@ -3,28 +3,23 @@ import com.axonivy.market.comparator.LatestVersionComparator; import com.axonivy.market.comparator.MavenVersionComparator; import com.axonivy.market.constants.CommonConstants; -import com.axonivy.market.constants.GitHubConstants; import com.axonivy.market.entity.Metadata; -import com.axonivy.market.entity.Product; -import com.axonivy.market.enums.NonStandardProduct; -import lombok.extern.log4j.Log4j2; import lombok.AccessLevel; import lombok.NoArgsConstructor; +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.GHTag; import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; + import static com.axonivy.market.constants.MavenConstants.*; @Log4j2 @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -45,20 +40,6 @@ public static List getVersionsToDisplay(List versions, Boolean i return versions.stream().filter(VersionUtils::isReleasedVersion).sorted(new LatestVersionComparator()).toList(); } - public static String getMavenVersionMatchWithTag(List releasedVersions, String mavenVersion) { - for (String version : releasedVersions) { - if (mavenVersion.equals(version)) { - return mavenVersion; - } - } - return getAlternativeVersion(releasedVersions, mavenVersion); - } - - public static String getAlternativeVersion(List releaseVersions, String version) { - return Optional.ofNullable(releaseVersions).orElse(List.of()).stream().filter( - version::startsWith).sorted().findAny().orElse(null); - } - public static String getBestMatchVersion(List versions, String designerVersion) { String bestMatchVersion = versions.stream().filter( version -> StringUtils.equals(version, designerVersion)).findAny().orElse(null); @@ -125,47 +106,17 @@ public static String getBugfixVersion(String version) { return version; } - public static String convertTagToVersion(String tag) { - if (StringUtils.isBlank(tag) || !StringUtils.startsWith(tag, GitHubConstants.STANDARD_TAG_PREFIX)) { - return tag; - } - return tag.substring(1); - } - - public static List convertTagsToVersions(List tags) { - Objects.requireNonNull(tags); - return tags.stream().map(VersionUtils::convertTagToVersion).toList(); - } - - public static String convertVersionToTag(String productId, String version) { - if (StringUtils.isBlank(version)) { - return version; - } - NonStandardProduct product = NonStandardProduct.findById(productId); - if (product.isVersionTagNumberOnly()) { - return version; - } - return GitHubConstants.STANDARD_TAG_PREFIX.concat(version); - } - - public static String getOldestVersion(List tags) { + public static String getOldestVersions(List versions) { String result = StringUtils.EMPTY; - if (!CollectionUtils.isEmpty(tags)) { - List releasedTags = tags.stream().map(tag -> tag.getName().replaceAll(NON_NUMERIC_CHAR, Strings.EMPTY)) + if (!CollectionUtils.isEmpty(versions)) { + List releasedVersions = versions.stream().map( + version -> version.replaceAll(NON_NUMERIC_CHAR, Strings.EMPTY)) .distinct().sorted(new LatestVersionComparator()).toList(); - result = CollectionUtils.lastElement(releasedTags); + return CollectionUtils.lastElement(releasedVersions); } return result; } - public static List getReleaseTagsFromProduct(Product product) { - if (Objects.isNull(product) || CollectionUtils.isEmpty(product.getReleasedVersions())) { - return new ArrayList<>(); - } - return product.getReleasedVersions().stream().map( - version -> convertVersionToTag(product.getId(), version)).toList(); - } - public static List removeSyncedVersionsFromReleasedVersions(List releasedVersion, Set syncTags) { if (ObjectUtils.isNotEmpty(syncTags)) { 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 22173bf16..834e0e56c 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java +++ b/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java @@ -2,12 +2,12 @@ import com.axonivy.market.bo.Artifact; import com.axonivy.market.constants.MavenConstants; +import com.axonivy.market.entity.Image; import com.axonivy.market.entity.MavenArtifactVersion; import com.axonivy.market.entity.Metadata; import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductDesignerInstallation; import com.axonivy.market.entity.ProductJsonContent; -import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.enums.Language; import com.axonivy.market.enums.SortOption; import com.axonivy.market.model.MavenArtifactModel; @@ -27,7 +27,6 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -40,36 +39,36 @@ public class BaseSetup { protected static final Pageable PAGEABLE = PageRequest.of(0, 20, Sort.by(SortOption.ALPHABETICALLY.getOption()).descending()); protected static final String MOCK_PRODUCT_ID = "bpmn-statistic"; - protected static final String MOCK_PRODUCT_ID_WITH_TAG = "bpmn-statistic-v10.0.10"; + protected static final String MOCK_PRODUCT_ID_WITH_VERSION = "bpmn-statistic-10.0.10"; protected static final String MOCK_ARTIFACT_ID = "bpmn-statistic"; protected static final String MOCK_PRODUCT_ARTIFACT_ID = "bpmn-statistic-product"; protected static final String MOCK_RELEASED_VERSION = "10.0.10"; protected static final String MOCK_SNAPSHOT_VERSION = "10.0.10-SNAPSHOT"; protected static final String MOCK_BUGFIX_VERSION = "10.0.10.1"; protected static final String MOCK_SPRINT_RELEASED_VERSION = "10.0.10-m123"; - protected static final String MOCK_TAG_FROM_RELEASED_VERSION = "v10.0.10"; - protected static final String MOCK_TAG_FROM_SNAPSHOT_VERSION = "v10.0.10-SNAPSHOT"; protected static final String MOCK_GROUP_ID = "com.axonivy.util"; protected static final String MOCK_PRODUCT_NAME = "bpmn statistic"; + protected static final String MOCK_PRODUCT_REPOSITORY_NAME = "axonivy-market/bpmn-statistic"; protected static final String MOCK_PRODUCT_JSON_FILE_PATH = "src/test/resources/product.json"; protected static final String MOCK_PRODUCT_JSON_FILE_PATH_NO_URL = "src/test/resources/productMissingURL.json"; + protected static final String MOCK_PRODUCT_JSON_WITH_DROPINS_FILE_PATH = "src/test/resources/product-dropins.json"; protected static final String MOCK_PRODUCT_JSON_DIR_PATH = "src/test/resources"; protected static final String MOCK_PRODUCT_JSON_NODE_FILE_PATH = "src/test/resources/prouct-json-node.json"; protected static final String MOCK_METADATA_FILE_PATH = "src/test/resources/metadata.xml"; protected static final String MOCK_SNAPSHOT_METADATA_FILE_PATH = "src/test/resources/snapshotMetadata.xml"; + protected static final String MOCK_README_FILE = "src/test/resources/README.md"; protected static final String INVALID_FILE_PATH = "test/file/path"; - protected static final String MOCK_SETUP_MD_PATH = "src/test/resources/setup.md"; protected static final String MOCK_MAVEN_URL = "https://maven.axonivy.com/com/axonivy/util/bpmn-statistic/maven" + "-metadata.xml"; protected static final String MOCK_SNAPSHOT_MAVEN_URL = "https://maven.axonivy.com/com/axonivy/util/bpmn-statistic" + "/10.0.10-SNAPSHOT/maven-metadata.xml"; protected static final String MOCK_DOWNLOAD_URL = "https://maven.axonivy.com/com/axonivy/util/bpmn-statistic/10.0" + ".10/bpmn-statistic-10.0.10.zip"; - protected static final String MOCK_SNAPSHOT_DOWNLOAD_URL = "https://maven.axonivy" + - ".com/com/axonivy/util/bpmn-statistic/10.0.10-SNAPSHOT/bpmn-statistic-10.0.10-SNAPSHOT.zip"; protected static final String MOCK_ARTIFACT_NAME = "bpmn statistic (zip)"; protected static final String MOCK_ARTIFACT_DOWNLOAD_FILE = "bpmn-statistic.zip"; protected static final String LEGACY_INSTALLATION_COUNT_PATH_FIELD_NAME = "legacyInstallationCountPath"; + protected static final String MOCK_IMAGE_URL = "https://raw.githubusercontent" + + ".com/amazon-comprehend-connector-product/images/comprehend-demo-sentiment.png"; protected Page createPageProductsMock() { var mockProducts = new ArrayList(); @@ -115,10 +114,6 @@ protected static ProductJsonContent getMockProductJsonContent() { return result; } - protected static String getMockSetupMd() { - return getContentFromTestResourcePath(MOCK_SETUP_MD_PATH); - } - private static String getContentFromTestResourcePath(String path) { try { return Files.readString(Paths.get(path)); @@ -132,10 +127,8 @@ protected static String getMockProductJsonNodeContent() { return getContentFromTestResourcePath(MOCK_PRODUCT_JSON_NODE_FILE_PATH); } - protected ProductModuleContent getMockProductModuleContent() { - ProductModuleContent mockProductModuleContent = new ProductModuleContent(); - mockProductModuleContent.setMavenVersions(new HashSet<>()); - return mockProductModuleContent; + protected static String getMockReadmeContent() { + return getContentFromTestResourcePath(MOCK_README_FILE); } protected Artifact getMockArtifact() { @@ -148,6 +141,34 @@ protected Artifact getMockArtifact() { return mockArtifact; } + protected Artifact getMockArtifact2() { + Artifact mockArtifact = new Artifact(); + mockArtifact.setIsDependency(true); + mockArtifact.setGroupId(MOCK_GROUP_ID); + mockArtifact.setArtifactId(MOCK_PRODUCT_ARTIFACT_ID); + mockArtifact.setType("zip"); + mockArtifact.setName(MOCK_PRODUCT_NAME); + return mockArtifact; + } + + public static Image getMockImage() { + Image image = new Image(); + image.setId("66e2b14868f2f95b2f95549a"); + image.setSha("914d9b6956db7a1404622f14265e435f36db81fa"); + image.setProductId(SAMPLE_PRODUCT_ID); + image.setImageUrl(MOCK_IMAGE_URL); + return image; + } + + public static Image getMockImage2() { + Image image = new Image(); + image.setId("66e2b14868f2f95b2f95550a"); + image.setSha("914d9b6956db7a1404622f14265e435f36db81fa"); + image.setProductId(SAMPLE_PRODUCT_ID); + image.setImageUrl(MOCK_IMAGE_URL); + return image; + } + protected String getMockSnapShotMetadataContent() { return getContentFromTestResourcePath(MOCK_SNAPSHOT_METADATA_FILE_PATH); } @@ -177,7 +198,7 @@ protected MavenArtifactVersion getMockMavenArtifactVersionWithData() { protected Product getMockProduct() { Product mockProduct = Product.builder().id(MOCK_PRODUCT_ID).releasedVersions(new ArrayList<>()).artifacts( - List.of(getMockArtifact())).build(); + List.of(getMockArtifact(), getMockArtifact2())).build(); mockProduct.getReleasedVersions().add(MOCK_RELEASED_VERSION); return mockProduct; } @@ -211,15 +232,14 @@ protected Metadata getMockMetadataWithVersions() { return mockMetadata; } - protected MavenArtifactModel getMockMavenArtifactModel() { - MavenArtifactModel mockMavenArtifactModel = new MavenArtifactModel(); - mockMavenArtifactModel.setName(MOCK_ARTIFACT_NAME); - mockMavenArtifactModel.setDownloadUrl(MOCK_DOWNLOAD_URL); - return mockMavenArtifactModel; - } - protected MavenArtifactModel getMockMavenArtifactModelWithDownloadUrl() { return MavenArtifactModel.builder().name(MOCK_PRODUCT_NAME).artifactId(MOCK_ARTIFACT_ID).downloadUrl( MOCK_DOWNLOAD_URL).build(); } + + protected static ProductJsonContent getMockProductJsonContentContainMavenDropins() { + ProductJsonContent result = new ProductJsonContent(); + result.setContent(getContentFromTestResourcePath(MOCK_PRODUCT_JSON_WITH_DROPINS_FILE_PATH)); + return result; + } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductControllerTest.java index e9d9b8135..9d21e2b52 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductControllerTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductControllerTest.java @@ -31,7 +31,11 @@ import org.springframework.hateoas.PagedModel.PageMetadata; import org.springframework.http.HttpStatus; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -113,7 +117,7 @@ void testFindProducts() { @Test void testSyncProductsSuccess() { - when(service.syncLatestDataFromMarketRepo()).thenReturn(List.of()); + when(service.syncLatestDataFromMarketRepo(false)).thenReturn(List.of()); var response = productController.syncProducts(AUTHORIZATION_HEADER, false); @@ -125,7 +129,7 @@ void testSyncProductsSuccess() { @Test void testSyncProductsWithResetSuccess() { - when(service.syncLatestDataFromMarketRepo()).thenReturn(List.of("portal")); + when(service.syncLatestDataFromMarketRepo(true)).thenReturn(List.of("portal")); var response = productController.syncProducts(AUTHORIZATION_HEADER, true); 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 f3a6bd72e..570d765a2 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 @@ -1,5 +1,6 @@ package com.axonivy.market.controller; +import com.axonivy.market.BaseSetup; import com.axonivy.market.assembler.ProductDetailModelAssembler; import com.axonivy.market.constants.RequestMappingConstants; import com.axonivy.market.entity.Product; @@ -8,8 +9,6 @@ import com.axonivy.market.model.MavenArtifactVersionModel; import com.axonivy.market.model.ProductDetailModel; import com.axonivy.market.model.VersionAndUrlModel; -import com.axonivy.market.service.ImageService; -import com.axonivy.market.service.ProductDesignerInstallationService; import com.axonivy.market.service.ProductService; import com.axonivy.market.service.VersionService; import com.fasterxml.jackson.databind.ObjectMapper; @@ -35,20 +34,11 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -class ProductDetailsControllerTest { - public static final String TAG = "v10.0.6"; +class ProductDetailsControllerTest extends BaseSetup { @Mock private ProductService productService; - - @Mock - private ImageService imageService; - @Mock VersionService versionService; - - @Mock - ProductDesignerInstallationService productDesignerInstallationService; - @Mock private ProductDetailModelAssembler detailModelAssembler; @@ -83,19 +73,19 @@ void testProductDetails() { void testFindBestMatchProductDetailsByVersion() { Mockito.when(productService.fetchBestMatchProductDetail(Mockito.anyString(), Mockito.anyString())).thenReturn( mockProduct()); - Mockito.when(detailModelAssembler.toModel(mockProduct(), TAG, + Mockito.when(detailModelAssembler.toModel(mockProduct(), MOCK_RELEASED_VERSION, RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION)).thenReturn(createProductMockWithDetails()); ResponseEntity mockExpectedResult = new ResponseEntity<>(createProductMockWithDetails(), HttpStatus.OK); ResponseEntity result = productDetailsController.findBestMatchProductDetailsByVersion( - DOCKER_CONNECTOR_ID, TAG); + DOCKER_CONNECTOR_ID, MOCK_RELEASED_VERSION); assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals(result, mockExpectedResult); - verify(productService, times(1)).fetchBestMatchProductDetail(DOCKER_CONNECTOR_ID, TAG); - verify(detailModelAssembler, times(1)).toModel(mockProduct(), TAG, + verify(productService, times(1)).fetchBestMatchProductDetail(DOCKER_CONNECTOR_ID, MOCK_RELEASED_VERSION); + verify(detailModelAssembler, times(1)).toModel(mockProduct(), MOCK_RELEASED_VERSION, RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION); } @@ -104,18 +94,19 @@ void testProductDetailsWithVersion() { Mockito.when(productService.fetchProductDetailByIdAndVersion(Mockito.anyString(), Mockito.anyString())).thenReturn( mockProduct()); Mockito.when( - detailModelAssembler.toModel(mockProduct(), TAG, RequestMappingConstants.BY_ID_AND_VERSION)).thenReturn( + detailModelAssembler.toModel(mockProduct(), MOCK_RELEASED_VERSION, + RequestMappingConstants.BY_ID_AND_VERSION)).thenReturn( createProductMockWithDetails()); ResponseEntity mockExpectedResult = new ResponseEntity<>(createProductMockWithDetails(), HttpStatus.OK); ResponseEntity result = productDetailsController.findProductDetailsByVersion( - DOCKER_CONNECTOR_ID, TAG); + DOCKER_CONNECTOR_ID, MOCK_RELEASED_VERSION); assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals(result, mockExpectedResult); - verify(productService, times(1)).fetchProductDetailByIdAndVersion(DOCKER_CONNECTOR_ID, TAG); + verify(productService, times(1)).fetchProductDetailByIdAndVersion(DOCKER_CONNECTOR_ID, MOCK_RELEASED_VERSION); } @Test @@ -124,11 +115,11 @@ void testProductDetailsWithVersionWithWrongProductId() { null); ResponseEntity result = productDetailsController.findProductDetailsByVersion( - WRONG_PRODUCT_ID, TAG); + WRONG_PRODUCT_ID, MOCK_RELEASED_VERSION); assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); - verify(productService, times(1)).fetchProductDetailByIdAndVersion(WRONG_PRODUCT_ID, TAG); + verify(productService, times(1)).fetchProductDetailByIdAndVersion(WRONG_PRODUCT_ID, MOCK_RELEASED_VERSION); } @Test @@ -137,11 +128,11 @@ void testBestMatchProductDetailsWithVersionWithWrongProductId() { null); ResponseEntity result = productDetailsController.findBestMatchProductDetailsByVersion( - WRONG_PRODUCT_ID, TAG); + WRONG_PRODUCT_ID, MOCK_RELEASED_VERSION); assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode()); - verify(productService, times(1)).fetchBestMatchProductDetail(WRONG_PRODUCT_ID, TAG); + verify(productService, times(1)).fetchBestMatchProductDetail(WRONG_PRODUCT_ID, MOCK_RELEASED_VERSION); } @Test @@ -210,13 +201,13 @@ private List mockVersionAndUrlModels() { } @Test - void findProductJsonContentByIdAndTag() throws IOException { + void findProductJsonContentByIdAndVersion() throws IOException { ProductJsonContent productJsonContent = mockProductJsonContent(); Map map = new ObjectMapper().readValue(productJsonContent.getContent(), Map.class); - when(versionService.getProductJsonContentByIdAndTag("bpmnstatistic", "10.0.21")).thenReturn( + when(versionService.getProductJsonContentByIdAndVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION)).thenReturn( map); - var result = productDetailsController.findProductJsonContent("bpmnstatistic", "10.0.21"); + var result = productDetailsController.findProductJsonContent(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); assertEquals(new ResponseEntity<>(map, HttpStatus.OK), result); } diff --git a/marketplace-service/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java b/marketplace-service/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java index a85e1e6f5..24a4a854b 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java @@ -13,7 +13,6 @@ import java.io.IOException; import java.io.InputStream; -import java.util.List; import static com.axonivy.market.constants.CommonConstants.SLASH; import static com.axonivy.market.constants.MetaConstants.META_FILE; @@ -80,17 +79,13 @@ void testExtractSourceUrl() { @Test void testTransferComputedData() { - String initialVersion = "10.0.2"; Product product = new Product(); Product persistedData = new Product(); persistedData.setCustomOrder(1); - persistedData.setReleasedVersions(List.of(initialVersion)); - persistedData.setNewestReleaseVersion(initialVersion); + persistedData.setInstallationCount(300); ProductFactory.transferComputedPersistedDataToProduct(persistedData, product); assertEquals(1, product.getCustomOrder()); - assertEquals(initialVersion, product.getNewestReleaseVersion()); - assertEquals(1, product.getReleasedVersions().size()); - assertEquals(initialVersion, product.getReleasedVersions().get(0)); + assertEquals(300, product.getInstallationCount()); } } \ No newline at end of file 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 4ed738baf..d04be35be 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 @@ -6,6 +6,7 @@ import com.axonivy.market.entity.ProductDesignerInstallation; import com.axonivy.market.repository.MavenArtifactVersionRepository; import com.axonivy.market.repository.MetadataRepository; +import com.axonivy.market.repository.ProductJsonContentRepository; import com.axonivy.market.repository.ProductModuleContentRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -21,13 +22,21 @@ import java.util.List; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class CustomProductRepositoryImplTest extends BaseSetup { @Mock ProductModuleContentRepository contentRepo; + @Mock + ProductJsonContentRepository jsonContentRepo; private Product mockProduct; private Aggregation mockAggregation; @Mock @@ -86,14 +95,14 @@ void testReleasedVersionsById_WhenResultIsNull() { @Test void testGetProductById() { setUpMockAggregateResult(); - Product actualProduct = repo.getProductById(MOCK_PRODUCT_ID); + Product actualProduct = repo.getProductWithModuleContent(MOCK_PRODUCT_ID); assertEquals(mockProduct, actualProduct); } @Test - void testGetProductByIdAndTag() { + void testGetProductByIdAndVersion() { setUpMockAggregateResult(); - Product actualProduct = repo.getProductByIdWithTagOrVersion(MOCK_PRODUCT_ID, MOCK_TAG_FROM_RELEASED_VERSION); + Product actualProduct = repo.getProductByIdAndVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); assertEquals(mockProduct, actualProduct); } diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/SystemTasksTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/SystemTasksTest.java index 723afb069..683c5f316 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/SystemTasksTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/SystemTasksTest.java @@ -42,6 +42,6 @@ void testSyncDoc() { @Test void testSyncProduct() { tasks.syncDataForProductFromGitHubRepo(); - verify(productService, times(1)).syncLatestDataFromMarketRepo(); + verify(productService, times(1)).syncLatestDataFromMarketRepo(false); } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/FileDownloadServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/FileDownloadServiceImplTest.java index 12ed8cfa3..4f02e3198 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/FileDownloadServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/FileDownloadServiceImplTest.java @@ -1,16 +1,12 @@ package com.axonivy.market.service.impl; -import com.axonivy.market.repository.ExternalDocumentMetaRepository; -import com.axonivy.market.repository.ProductRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; -import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.web.client.ResourceAccessException; -import org.springframework.web.client.RestTemplate; import java.io.File; import java.io.IOException; @@ -27,12 +23,6 @@ class FileDownloadServiceImplTest { private static final String EXTRACT_DIR_LOCATION = "src/test/resources/zip/data"; private static final String EXTRACTED_DIR_LOCATION = "src/test/resources/zip/data/text"; private static final String DOWNLOAD_URL = "https://repo/axonivy/portal/portal-guide/10.0.0/portal-guide-10.0.0.zip"; - @Mock - ProductRepository productRepository; - @Mock - ExternalDocumentMetaRepository externalDocumentMetaRepository; - @Mock - private RestTemplate restTemplate; @InjectMocks private FileDownloadServiceImpl fileDownloadService; diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyProductRepoServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyProductRepoServiceImplTest.java index d554ad6d8..209e7f236 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyProductRepoServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyProductRepoServiceImplTest.java @@ -2,17 +2,12 @@ import com.axonivy.market.BaseSetup; import com.axonivy.market.bo.Artifact; -import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.constants.MavenConstants; import com.axonivy.market.constants.ProductJsonConstants; -import com.axonivy.market.constants.ReadmeConstants; -import com.axonivy.market.entity.Image; -import com.axonivy.market.entity.Product; -import com.axonivy.market.enums.Language; +import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.github.service.GitHubService; import com.axonivy.market.github.service.impl.GHAxonIvyProductRepoServiceImpl; import com.axonivy.market.github.util.GitHubUtils; -import com.axonivy.market.repository.ProductJsonContentRepository; import com.axonivy.market.service.ImageService; import com.axonivy.market.util.MavenUtils; import com.axonivy.market.util.ProductContentUtils; @@ -26,10 +21,10 @@ import org.kohsuke.github.GHContent; import org.kohsuke.github.GHOrganization; import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHTag; import org.kohsuke.github.PagedIterable; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; @@ -42,29 +37,24 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Map; - -import static com.axonivy.market.constants.GitHubConstants.MG_GRAPH_IMAGES_FOR_SETUP_FILE; -import static com.axonivy.market.constants.GitHubConstants.MS_GRAPH_PRODUCT_DIRECTORY; -import static com.axonivy.market.constants.MongoDBConstants.TAG; -import static com.axonivy.market.constants.ProductJsonConstants.EN_LANGUAGE; -import static com.axonivy.market.constants.ReadmeConstants.SETUP_FILE; -import static com.axonivy.market.enums.NonStandardProduct.MICROSOFT_TEAMS; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class GHAxonIvyProductRepoServiceImplTest extends BaseSetup { public static final String IMAGE_NAME = "image.png"; - public static final String DOCUWARE_CONNECTOR_PRODUCT = "docuware-connector-product"; private final ObjectMapper objectMapper = new ObjectMapper(); - @Mock - PagedIterable listTags; - @Mock GHRepository ghRepository; @@ -82,9 +72,6 @@ class GHAxonIvyProductRepoServiceImplTest extends BaseSetup { @Mock ImageService imageService; - @Mock - ProductJsonContentRepository productJsonContentRepository; - @InjectMocks @Spy private GHAxonIvyProductRepoServiceImpl axonivyProductRepoServiceImpl; @@ -94,13 +81,6 @@ void setup() throws IOException { when(mockGHOrganization.getRepository(any())).thenReturn(ghRepository); } - private static GHContent createMockImage() { - GHContent mockImage = mock(GHContent.class); - when(mockImage.isFile()).thenReturn(true); - when(mockImage.getName()).thenReturn(IMAGE_NAME); - return mockImage; - } - @AfterEach void after() { Mockito.reset(mockGHOrganization); @@ -117,18 +97,6 @@ void testContentFromGHRepoAndTag() throws IOException { assertNull(result); } - @Test - void testAllTagsFromRepoName() throws IOException { - setup(); - var mockTag = mock(GHTag.class); - when(mockTag.getName()).thenReturn(MOCK_TAG_FROM_RELEASED_VERSION); - when(listTags.toList()).thenReturn(List.of(mockTag)); - when(ghRepository.listTags()).thenReturn(listTags); - var result = axonivyProductRepoServiceImpl.getAllTagsFromRepoName(StringUtils.EMPTY); - assertEquals(1, result.size()); - assertEquals(MOCK_TAG_FROM_RELEASED_VERSION, result.get(0).getName()); - } - @Test void testExtractMavenArtifactFromJsonNode() throws JsonProcessingException { List artifacts = new ArrayList<>(); @@ -146,40 +114,13 @@ void testExtractMavenArtifactFromJsonNode() throws JsonProcessingException { assertEquals(MOCK_GROUP_ID, artifacts.get(1).getGroupId()); } - private static void getReadmeInputStream(String readmeContentString, GHContent mockContent) throws IOException { - InputStream mockReadmeInputStream = mock(InputStream.class); - when(mockContent.read()).thenReturn(mockReadmeInputStream); - when(mockReadmeInputStream.readAllBytes()).thenReturn(readmeContentString.getBytes()); - } - @Test void testGetOrganization() throws IOException { - when(gitHubService.getOrganization(Mockito.anyString())).thenReturn(mockGHOrganization); + when(gitHubService.getOrganization(anyString())).thenReturn(mockGHOrganization); assertEquals(mockGHOrganization, axonivyProductRepoServiceImpl.getOrganization()); assertEquals(mockGHOrganization, axonivyProductRepoServiceImpl.getOrganization()); } - @Test - void testGetReadmeAndProductContentsFromTag() throws IOException { - String readmeContentWithImage = """ - #Product-name - Test README - ## Demo - Demo content - ## Setup - Setup content (image.png) - """; - testGetReadmeAndProductContentsFromTagWithReadmeText(readmeContentWithImage); - String readmeContentWithoutHashProductName = """ - Test README - ## Demo - Demo content - ## Setup - Setup content (image.png) - """; - testGetReadmeAndProductContentsFromTagWithReadmeText(readmeContentWithoutHashProductName); - } - @Test void testCreateArtifactFromJsonNode() { String repoUrl = MavenConstants.DEFAULT_IVY_MAVEN_BASE_URL; @@ -208,18 +149,8 @@ void testCreateArtifactFromJsonNode() { assertTrue(artifact.getIsProductArtifact()); } - public static Image mockImage() { - Image image = new Image(); - image.setId("66e2b14868f2f95b2f95549a"); - image.setSha("914d9b6956db7a1404622f14265e435f36db81fa"); - image.setProductId("amazon-comprehend"); - image.setImageUrl( - "https://raw.githubusercontent.com/amazon-comprehend-connector-product/images/comprehend-demo-sentiment.png"); - return image; - } - @Test - void testGetReadmeAndProductContentFromTag_ImageFromRootFolder() { + void testExtractReadMeFileFromContents_ImageFromRootFolder() { String readmeContentWithImageFolder = """ #Product-name Test README @@ -230,7 +161,7 @@ void testGetReadmeAndProductContentFromTag_ImageFromRootFolder() { GHContent mockImageFile = mock(GHContent.class); when(mockImageFile.getName()).thenReturn(IMAGE_NAME); - when(imageService.mappingImageFromGHContent(any(), any(), anyBoolean())).thenReturn(mockImage()); + when(imageService.mappingImageFromGHContent(any(), any())).thenReturn(getMockImage()); String updatedReadme = axonivyProductRepoServiceImpl.updateImagesWithDownloadUrl(BaseSetup.MOCK_PRODUCT_ID, List.of(mockImageFile), readmeContentWithImageFolder); @@ -246,7 +177,7 @@ Setup content (imageId-66e2b14868f2f95b2f95549a)""", } @Test - void testGetReadmeAndProductContentFromTag_ImageFromChildFolder() throws IOException { + void testExtractReadMeFileFromContents_ImageFromChildFolder() throws IOException { String readmeContentWithImageFolder = """ #Product-name Test README @@ -272,7 +203,7 @@ void testGetReadmeAndProductContentFromTag_ImageFromChildFolder() throws IOExcep when(mockImageFile2.listDirectoryContent()).thenReturn(pagedIterable2); when(pagedIterable2.toList()).thenReturn(List.of(mockImageFile3)); - when(imageService.mappingImageFromGHContent(any(), any(), anyBoolean())).thenReturn(mockImage()); + when(imageService.mappingImageFromGHContent(any(), any())).thenReturn(getMockImage()); String updatedReadme = axonivyProductRepoServiceImpl.updateImagesWithDownloadUrl(BaseSetup.MOCK_PRODUCT_ID, List.of(mockImageFile), readmeContentWithImageFolder); @@ -287,48 +218,32 @@ Setup content (imageId-66e2b14868f2f95b2f95549a)""", updatedReadme); } - private void testGetReadmeAndProductContentsFromTagWithReadmeText(String readmeContentWithImage) throws IOException { - //Mock readme content - GHContent mockContent = mock(GHContent.class); - when(mockContent.isDirectory()).thenReturn(true); - when(mockContent.isFile()).thenReturn(true); - when(mockContent.getName()).thenReturn(DOCUWARE_CONNECTOR_PRODUCT, ReadmeConstants.README_FILE); - - PagedIterable pagedIterable = mock(String.valueOf(GHContent.class)); - when(mockContent.listDirectoryContent()).thenReturn(pagedIterable); - when(pagedIterable.toList()).thenReturn(List.of()); - - getReadmeInputStream(readmeContentWithImage, mockContent); - - GHContent mockContent2 = createMockImage(); - - when(ghRepository.getDirectoryContent(CommonConstants.SLASH, MOCK_TAG_FROM_RELEASED_VERSION)).thenReturn( - List.of(mockContent, mockContent2)); - when(ghRepository.getDirectoryContent(DOCUWARE_CONNECTOR_PRODUCT, MOCK_TAG_FROM_RELEASED_VERSION)).thenReturn( - List.of(mockContent, mockContent2)); - when(imageService.mappingImageFromGHContent(any(), any(), anyBoolean())).thenReturn(mockImage()); - var result = axonivyProductRepoServiceImpl.getReadmeAndProductContentsFromTag(getMockProducts().get(0), - ghRepository, - MOCK_TAG_FROM_RELEASED_VERSION); - - assertEquals(MOCK_TAG_FROM_RELEASED_VERSION, result.getTag()); - assertEquals("Test README", result.getDescription().get(Language.EN.getValue())); - assertEquals("Demo content", result.getDemo().get(Language.EN.getValue())); - assertEquals("Setup content (imageId-66e2b14868f2f95b2f95549a)", result.getSetup().get(Language.EN.getValue())); - } - @Test - void testGetReadmeAndProductContentsFromTag_WithNoFullyThreeParts() throws IOException { - String readmeContentString = "#Product-name\n Test README\n## Setup\nSetup content"; - GHContent mockContent = createMockProductFolder(); - getReadmeInputStream(readmeContentString, mockContent); - - var result = axonivyProductRepoServiceImpl.getReadmeAndProductContentsFromTag(getMockProducts().get(0), - ghRepository, - MOCK_TAG_FROM_RELEASED_VERSION); - - assertNull(result.getArtifactId()); - assertEquals("Setup content", result.getSetup().get(Language.EN.getValue())); + void testExtractReadMeFileFromContents() throws IOException { + try (MockedStatic mockedProductContentUtils = Mockito.mockStatic(ProductContentUtils.class)) { + GHContent mockReadmeFile = mock(GHContent.class); + when(mockReadmeFile.isFile()).thenReturn(true); + when(mockReadmeFile.getName()).thenReturn("README.md"); + when(mockReadmeFile.read()).thenReturn(new ByteArrayInputStream("Test README content".getBytes())); + + GHContent mockImageFile = mock(GHContent.class); + when(mockImageFile.isFile()).thenReturn(true); + when(mockImageFile.getName()).thenReturn("image.png"); + + List contents = List.of(mockReadmeFile, mockImageFile); + + when(ProductContentUtils.hasImageDirectives(anyString())).thenReturn(true); + when(imageService.mappingImageFromGHContent(anyString(), any())).thenReturn(getMockImage()); + ProductModuleContent productModuleContent = new ProductModuleContent(); + + axonivyProductRepoServiceImpl.extractReadMeFileFromContents(getMockProducts().get(0), contents, + productModuleContent); + + verify(mockReadmeFile, times(1)).read(); + verify(imageService, times(1)).mappingImageFromGHContent(anyString(), eq(mockImageFile)); + mockedProductContentUtils.verify( + () -> ProductContentUtils.updateProductModuleTabContents(productModuleContent, new HashMap<>())); + } } @Test @@ -344,19 +259,6 @@ void testConvertProductJsonToMavenProductInfo() throws IOException { assertTrue(CollectionUtils.isEmpty(GitHubUtils.convertProductJsonToMavenProductInfo(content))); } - @Test - void testGetReadmeAndProductContentsFromTag_SwitchPartsPosition() throws IOException { - String readmeContentString = "#Product-name\n Test README\n## Setup\nSetup content\n## Demo\nDemo content"; - GHContent mockContent = createMockProductFolder(); - getReadmeInputStream(readmeContentString, mockContent); - - var result = axonivyProductRepoServiceImpl.getReadmeAndProductContentsFromTag(getMockProducts().get(0), - ghRepository, - MOCK_TAG_FROM_RELEASED_VERSION); - assertEquals("Demo content", result.getDemo().get(Language.EN.getValue())); - assertEquals("Setup content", result.getSetup().get(Language.EN.getValue())); - } - private static InputStream getMockInputStreamWithOutProjectAndDependency() { String jsonContent = """ { @@ -376,67 +278,9 @@ private static InputStream getMockInputStreamWithOutProjectAndDependency() { return new ByteArrayInputStream(jsonContent.getBytes(StandardCharsets.UTF_8)); } - private GHContent createMockProductFolder() throws IOException { - GHContent mockContent = mock(GHContent.class); - when(mockContent.isDirectory()).thenReturn(true); - when(mockContent.isFile()).thenReturn(true); - when(mockContent.getName()).thenReturn(DOCUWARE_CONNECTOR_PRODUCT, ReadmeConstants.README_FILE); - - when(ghRepository.getDirectoryContent(CommonConstants.SLASH, MOCK_TAG_FROM_RELEASED_VERSION)).thenReturn( - List.of(mockContent)); - when(ghRepository.getDirectoryContent(DOCUWARE_CONNECTOR_PRODUCT, MOCK_TAG_FROM_RELEASED_VERSION)).thenReturn( - List.of(mockContent)); - - return mockContent; - } - @Test void testExtractedContentStream() { assertNull(GitHubUtils.extractedContentStream(null)); assertNull(GitHubUtils.extractedContentStream(content)); } - - @Test - void testUpdateProductModuleContentSetupFromSetupMd() throws IOException { - when(gitHubService.getRepository(anyString())).thenReturn(ghRepository); - - InputStream mockReadmeInputStream = mock(InputStream.class); - - String setupStringContent = getMockSetupMd(); - - GHContent setupFileContent = mock(GHContent.class); - when(setupFileContent.isFile()).thenReturn(true); - when(setupFileContent.getName()).thenReturn(SETUP_FILE); - when(setupFileContent.read()).thenReturn(mockReadmeInputStream); - when(mockReadmeInputStream.readAllBytes()).thenReturn(setupStringContent.getBytes()); - - GHContent setupImageContent = mock(GHContent.class); - when(setupImageContent.isDirectory()).thenReturn(true); - when(setupImageContent.getName()).thenReturn(MG_GRAPH_IMAGES_FOR_SETUP_FILE); - - GHContent mockImageFile3 = mock(GHContent.class); - when(mockImageFile3.getName()).thenReturn(IMAGE_NAME); - - PagedIterable pagedIterable = mock(String.valueOf(GHContent.class)); - when(setupImageContent.listDirectoryContent()).thenReturn(pagedIterable); - when(pagedIterable.toList()).thenReturn(List.of(mockImageFile3)); - - when(ghRepository.getDirectoryContent(MS_GRAPH_PRODUCT_DIRECTORY, TAG)) - .thenReturn(List.of(setupFileContent, setupImageContent)); - - when(imageService.mappingImageFromGHContent(any(), any(), anyBoolean())).thenReturn(mockImage()); - - Product mockProduct = Product.builder() - .id(MICROSOFT_TEAMS.getId()) - .repositoryName("market/connector/microsoft365/chat/") - .build(); - Map> moduleContents = new HashMap<>(); - - moduleContents.put(ProductContentUtils.SETUP, new HashMap<>(Map.of(EN_LANGUAGE, "setup file content"))); - - axonivyProductRepoServiceImpl.updateSetupPartForProductModuleContent(mockProduct, moduleContents, TAG); - - assertTrue(moduleContents.get(ProductContentUtils.SETUP).get(EN_LANGUAGE).contains(ProductContentUtils.removeFirstLine( - setupStringContent.replace(IMAGE_NAME, "imageId-66e2b14868f2f95b2f95549a")))); - } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ImageServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ImageServiceImplTest.java index 5b5cb746f..789be9b0f 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ImageServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ImageServiceImplTest.java @@ -30,12 +30,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.times; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class ImageServiceImplTest extends BaseSetup { @@ -58,7 +58,7 @@ void testMappingImageFromGHContent() throws IOException { InputStream inputStream = this.getClass().getResourceAsStream(SLASH.concat(META_FILE)); when(content.read()).thenReturn(inputStream); - imageService.mappingImageFromGHContent(GOOGLE_MAPS_CONNECTOR, content, true); + imageService.mappingImageFromGHContent(GOOGLE_MAPS_CONNECTOR, content); Image expectedImage = new Image(); expectedImage.setProductId("google-maps-connector"); @@ -71,8 +71,8 @@ void testMappingImageFromGHContent() throws IOException { assertEquals(argumentCaptor.getValue().getSha(), expectedImage.getSha()); assertEquals(argumentCaptor.getValue().getImageUrl(), expectedImage.getImageUrl()); - when(imageRepository.findByProductIdAndSha(anyString(), anyString())).thenReturn(expectedImage); - Image result = imageService.mappingImageFromGHContent(GOOGLE_MAPS_CONNECTOR, content, false); + when(imageRepository.findByProductIdAndSha(anyString(), anyString())).thenReturn(List.of(expectedImage)); + Image result = imageService.mappingImageFromGHContent(GOOGLE_MAPS_CONNECTOR, content); assertEquals(expectedImage, result); } @@ -86,7 +86,7 @@ void testMappingImageFromGHContent_getImageFromDownloadUrl() throws IOException when(content.read()).thenThrow(new UnsupportedOperationException("Unrecognized encoding")); when(fileDownloadService.downloadFile(MOCK_MAVEN_URL)).thenReturn("content".getBytes()); - imageService.mappingImageFromGHContent(GOOGLE_MAPS_CONNECTOR, content, false); + imageService.mappingImageFromGHContent(GOOGLE_MAPS_CONNECTOR, content); verify(imageRepository).save(argumentCaptor.capture()); verify(fileDownloadService, times(1)).downloadFile(MOCK_MAVEN_URL); @@ -163,7 +163,7 @@ void testMappingImageFromDownloadedFolder_ReturnNull() { @Test void testMappingImageFromGHContent_noGhContent() { - var result = imageService.mappingImageFromGHContent(GOOGLE_MAPS_CONNECTOR, null, true); + var result = imageService.mappingImageFromGHContent(GOOGLE_MAPS_CONNECTOR, null); assertNull(result); } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/MetadataServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/MetadataServiceImplTest.java index 081a4c9ad..07b7b7490 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/MetadataServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/MetadataServiceImplTest.java @@ -5,13 +5,11 @@ import com.axonivy.market.entity.MavenArtifactVersion; import com.axonivy.market.entity.Metadata; import com.axonivy.market.entity.Product; -import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.model.MavenArtifactModel; import com.axonivy.market.repository.MavenArtifactVersionRepository; import com.axonivy.market.repository.MetadataRepository; import com.axonivy.market.repository.MetadataSyncRepository; import com.axonivy.market.repository.ProductJsonContentRepository; -import com.axonivy.market.repository.ProductModuleContentRepository; import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.util.MavenUtils; import lombok.extern.log4j.Log4j2; @@ -25,6 +23,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.util.CollectionUtils; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -38,15 +37,13 @@ class MetadataServiceImplTest extends BaseSetup { @Mock ProductRepository productRepo; @Mock - MetadataSyncRepository metadataSyncRepo; - @Mock ProductJsonContentRepository productJsonRepo; @Mock - MavenArtifactVersionRepository mavenArtifactVersionRepo; + private MetadataRepository metadataRepo; @Mock - MetadataRepository metadataRepo; + private MavenArtifactVersionRepository mavenArtifactVersionRepo; @Mock - ProductModuleContentRepository productContentRepo; + private MetadataSyncRepository metadataSyncRepo; @Test void testGetArtifactsFromNonSyncedVersion() { @@ -130,59 +127,32 @@ void testUpdateMavenArtifactVersionFromMetadata() { Assertions.assertEquals(2, mockMavenArtifactVersion.getAdditionalArtifactsByVersion().entrySet().size()); } } + @Test void testSyncAllProductsMetadata() { Mockito.when(productRepo.getAllProductsWithIdAndReleaseTagAndArtifact()).thenReturn(List.of(new Product())); + Mockito.when(metadataRepo.findByProductId(Mockito.isNull())).thenReturn(new ArrayList<>()); int result = metadataService.syncAllProductsMetadata(); - Assertions.assertEquals(1,result); + Assertions.assertEquals(1, result); Mockito.when(productRepo.getAllProductsWithIdAndReleaseTagAndArtifact()).thenReturn(getMockProducts()); result = metadataService.syncAllProductsMetadata(); - Assertions.assertEquals(0,result); - } - @Test - void testGetNonMatchSnapshotVersions() { - List releasedVersion = List.of(MOCK_SNAPSHOT_VERSION); - Set metaVersions = Set.of(MOCK_SNAPSHOT_VERSION); - ProductModuleContent mockProductModuleContent = getMockProductModuleContent(); - Mockito.when( - productContentRepo.findByTagAndProductId(MOCK_TAG_FROM_SNAPSHOT_VERSION, MOCK_PRODUCT_ID)).thenReturn( - mockProductModuleContent); - Assertions.assertTrue(CollectionUtils.isEmpty( - metadataService.getNonMatchSnapshotVersions(MOCK_PRODUCT_ID, releasedVersion, metaVersions))); - metaVersions = Set.of("2.0.0-SNAPSHOT"); - Assertions.assertEquals(1, - metadataService.getNonMatchSnapshotVersions(MOCK_PRODUCT_ID, releasedVersion, metaVersions).size()); - metaVersions = Set.of(MOCK_RELEASED_VERSION); - Assertions.assertTrue(CollectionUtils.isEmpty( - metadataService.getNonMatchSnapshotVersions(MOCK_PRODUCT_ID, releasedVersion, metaVersions))); - } - - - @Test - void testBuildProductFolderDownloadUrl() { - Metadata mockMetadata = getMockMetadata(); - Assertions.assertEquals(MOCK_SNAPSHOT_DOWNLOAD_URL, metadataService.buildProductFolderDownloadUrl(mockMetadata, - MOCK_SNAPSHOT_VERSION)); + Assertions.assertEquals(0, result); } @Test void testUpdateMavenArtifactVersionData() { - List releasedVersion = List.of(MOCK_RELEASED_VERSION); Metadata mockMetadata = getMockMetadata(); mockMetadata.setVersions(new HashSet<>()); mockMetadata.setUrl(MOCK_MAVEN_URL); Set mockMetadataSet = Set.of(mockMetadata); MavenArtifactVersion mockMavenArtifactVersion = getMockMavenArtifactVersion(); - metadataService.updateMavenArtifactVersionData(MOCK_PRODUCT_ID, releasedVersion, mockMetadataSet, + metadataService.updateMavenArtifactVersionData(mockMetadataSet, mockMavenArtifactVersion); Assertions.assertEquals(0, mockMavenArtifactVersion.getAdditionalArtifactsByVersion().size()); Assertions.assertEquals(0, mockMavenArtifactVersion.getProductArtifactsByVersion().size()); try (MockedStatic mockUtils = Mockito.mockStatic(MavenUtils.class)) { mockUtils.when(() -> MavenUtils.getMetadataContentFromUrl(MOCK_MAVEN_URL)).thenReturn(getMockMetadataContent()); - Mockito.when( - productContentRepo.findByTagAndProductId(MOCK_TAG_FROM_RELEASED_VERSION, MOCK_PRODUCT_ID)).thenReturn( - getMockProductModuleContent()); - metadataService.updateMavenArtifactVersionData(MOCK_PRODUCT_ID, releasedVersion, mockMetadataSet, + metadataService.updateMavenArtifactVersionData(mockMetadataSet, mockMavenArtifactVersion); Assertions.assertEquals(2, mockMavenArtifactVersion.getProductArtifactsByVersion().size()); } diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductContentServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductContentServiceImplTest.java new file mode 100644 index 000000000..bd9dc623b --- /dev/null +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductContentServiceImplTest.java @@ -0,0 +1,82 @@ +package com.axonivy.market.service.impl; + +import com.axonivy.market.BaseSetup; +import com.axonivy.market.bo.Artifact; +import com.axonivy.market.constants.ProductJsonConstants; +import com.axonivy.market.entity.ProductModuleContent; +import com.axonivy.market.service.ImageService; +import com.axonivy.market.service.ProductJsonContentService; +import com.axonivy.market.util.MavenUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ProductContentServiceImplTest extends BaseSetup { + private static final String EXTRACT_DIR_LOCATION = "src/test/resources/zip/data"; + @InjectMocks + private ProductContentServiceImpl productContentService; + @Mock + private ProductJsonContentService productJsonContentService; + @Mock + private ImageService imageService; + + @Test + void testUpdateDependencyContentsFromProductJson() throws IOException { + List mockArtifacts = List.of(mock(Artifact.class)); + try (MockedStatic mockedMavenUtils = Mockito.mockStatic(MavenUtils.class)) { + mockedMavenUtils.when( + () -> MavenUtils.convertProductJsonToMavenProductInfo(Paths.get(EXTRACT_DIR_LOCATION))).thenReturn( + mockArtifacts); + when( + MavenUtils.extractProductJsonContent(Paths.get(EXTRACT_DIR_LOCATION, ProductJsonConstants.PRODUCT_JSON_FILE))) + .thenReturn(getMockProductJsonNodeContent()); + + productContentService.updateDependencyContentsFromProductJson(new ProductModuleContent(), MOCK_PRODUCT_ID, + EXTRACT_DIR_LOCATION, MOCK_PRODUCT_NAME); + + verify(productJsonContentService, times(1)) + .updateProductJsonContent(getMockProductJsonNodeContent(), new ProductModuleContent().getVersion(), + ProductJsonConstants.VERSION_VALUE, MOCK_PRODUCT_ID, MOCK_PRODUCT_NAME); + } + } + + @Test + void testUpdateImagesWithDownloadUrl() throws IOException { + String readmeContent = getMockReadmeContent(); + String productId = MOCK_PRODUCT_ID; + try (MockedStatic mockedFiles = Mockito.mockStatic(Files.class)) { + Path downloadLocation = Paths.get(EXTRACT_DIR_LOCATION); + Path imagePath1 = Paths.get("screen1.png"); + Path imagePath2 = Paths.get("screen2.png"); + + when(Files.walk(downloadLocation)).thenReturn(Stream.of(downloadLocation, imagePath1, imagePath2)); + mockedFiles.when(() -> Files.isRegularFile(imagePath1)).thenReturn(true); + mockedFiles.when(() -> Files.isRegularFile(imagePath2)).thenReturn(true); + + when(imageService.mappingImageFromDownloadedFolder(productId, imagePath1)).thenReturn(getMockImage()); + when(imageService.mappingImageFromDownloadedFolder(productId, imagePath2)).thenReturn(getMockImage2()); + + String result = productContentService.updateImagesWithDownloadUrl(productId, EXTRACT_DIR_LOCATION, + readmeContent); + String expectedResult = readmeContent.replace("screen1.png \"Screen 1\"", + "imageId-66e2b14868f2f95b2f95549a").replace("screen2.png " + "\"Screen 2\"", + "imageId-66e2b14868f2f95b2f95550a"); + assertEquals(expectedResult, result); + } + } +} \ No newline at end of file diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductJsonContentServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductJsonContentServiceImplTest.java index baee0cc85..b15fa0f43 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductJsonContentServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductJsonContentServiceImplTest.java @@ -5,12 +5,14 @@ import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductJsonContent; import com.axonivy.market.repository.ProductJsonContentRepository; +import com.axonivy.market.repository.ProductRepository; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import java.util.HashMap; @@ -18,13 +20,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; -import org.mockito.Mockito; - @ExtendWith(MockitoExtension.class) class ProductJsonContentServiceImplTest extends BaseSetup { @Mock private ProductJsonContentRepository productJsonRepo; + @Mock + private ProductRepository productRepo; @InjectMocks private ProductJsonContentServiceImpl productJsonContentService; @@ -38,9 +40,8 @@ void testUpdateProductJsonContent_ValidJsonContent() { names.put(ProductJsonConstants.EN_LANGUAGE, MOCK_PRODUCT_NAME); product.setNames(names); - productJsonContentService.updateProductJsonContent(jsonContent, MOCK_TAG_FROM_RELEASED_VERSION, - MOCK_RELEASED_VERSION, - ProductJsonConstants.VERSION_VALUE, product); + productJsonContentService.updateProductJsonContent(jsonContent, MOCK_RELEASED_VERSION, + ProductJsonConstants.VERSION_VALUE, MOCK_PRODUCT_ID, MOCK_PRODUCT_NAME); ArgumentCaptor captor = ArgumentCaptor.forClass(ProductJsonContent.class); Mockito.verify(productJsonRepo).save(captor.capture()); @@ -49,14 +50,13 @@ void testUpdateProductJsonContent_ValidJsonContent() { assertEquals(MOCK_RELEASED_VERSION, savedContent.getVersion()); assertEquals(MOCK_PRODUCT_ID, savedContent.getProductId()); assertEquals(MOCK_PRODUCT_NAME, savedContent.getName()); - assertEquals("{\"version\":\"10.0.10\"}", savedContent.getContent()); + assertEquals("{\"version\":\"" + MOCK_RELEASED_VERSION + "\"}", savedContent.getContent()); } @Test void testUpdateProductJsonContent_EmptyJsonContent() { - Product product = new Product(); - productJsonContentService.updateProductJsonContent(StringUtils.EMPTY, MOCK_TAG_FROM_SNAPSHOT_VERSION, - MOCK_SNAPSHOT_VERSION, ProductJsonConstants.VERSION_VALUE, product); + productJsonContentService.updateProductJsonContent(StringUtils.EMPTY, MOCK_SNAPSHOT_VERSION, + ProductJsonConstants.VERSION_VALUE, MOCK_PRODUCT_ID, MOCK_PRODUCT_NAME); Mockito.verify(productJsonRepo, Mockito.never()).save(any(ProductJsonContent.class)); } } \ No newline at end of file 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 897b97ddc..7b5558424 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 @@ -2,6 +2,7 @@ import com.axonivy.market.BaseSetup; import com.axonivy.market.constants.GitHubConstants; +import com.axonivy.market.constants.ProductJsonConstants; import com.axonivy.market.criteria.ProductSearchCriteria; import com.axonivy.market.entity.GitHubRepoMeta; import com.axonivy.market.entity.MavenArtifactVersion; @@ -32,6 +33,8 @@ import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.service.ImageService; import com.axonivy.market.service.MetadataService; +import com.axonivy.market.service.ProductContentService; +import com.axonivy.market.util.MavenUtils; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -39,11 +42,12 @@ import org.kohsuke.github.GHCommit; import org.kohsuke.github.GHContent; import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHTag; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -58,7 +62,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -119,15 +122,17 @@ class ProductServiceImplTest extends BaseSetup { @Mock private MetadataRepository metadataRepo; @Mock - private MetadataSyncRepository metadataSyncRepo; - @Mock private ProductCustomSortRepository productCustomSortRepo; @Mock - private GHAxonIvyProductRepoService ghAxonIvyProductRepoService; - @Mock private ImageService imageService; @Mock private MavenArtifactVersionRepository mavenArtifactVersionRepo; + @Mock + private ProductContentService productContentService; + @Mock + private GHAxonIvyProductRepoService axonIvyProductRepoService; + @Mock + private MetadataSyncRepository metadataSyncRepo; @InjectMocks private ProductServiceImpl productService; @@ -143,7 +148,7 @@ void testUpdateInstallationCountForProduct() { Product product = getMockProduct(); product.setSynchronizedInstallationCount(true); - when(productRepo.getProductById(MOCK_PRODUCT_ID)).thenReturn(product); + when(productRepo.getProductWithModuleContent(MOCK_PRODUCT_ID)).thenReturn(product); when(productRepo.increaseInstallationCount(MOCK_PRODUCT_ID)).thenReturn(31); result = productService.updateInstallationCountForProduct(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); @@ -224,7 +229,7 @@ void testSyncProductsAsUpdateMetaJSONFromGitHub() throws IOException { when(productRepo.save(any(Product.class))).thenReturn(new Product()); // Executes - var result = productService.syncLatestDataFromMarketRepo(); + var result = productService.syncLatestDataFromMarketRepo(false); assertNotNull(result); assertTrue(result.isEmpty()); @@ -234,7 +239,7 @@ void testSyncProductsAsUpdateMetaJSONFromGitHub() throws IOException { when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit); mockGithubFile.setStatus(FileStatus.REMOVED); // Executes - result = productService.syncLatestDataFromMarketRepo(); + result = productService.syncLatestDataFromMarketRepo(false); assertNotNull(result); assertTrue(result.isEmpty()); } @@ -254,7 +259,7 @@ void testSyncProductsAsUpdateLogoFromGitHub() throws IOException { when(marketRepoService.fetchMarketItemsBySHA1Range(any(), any())).thenReturn(List.of(mockGitHubFile)); // Executes - var result = productService.syncLatestDataFromMarketRepo(); + var result = productService.syncLatestDataFromMarketRepo(false); assertNotNull(result); assertTrue(result.isEmpty()); @@ -264,7 +269,7 @@ void testSyncProductsAsUpdateLogoFromGitHub() throws IOException { when(marketRepoService.fetchMarketItemsBySHA1Range(any(), any())).thenReturn(List.of(mockGitHubFile)); // Executes - result = productService.syncLatestDataFromMarketRepo(); + result = productService.syncLatestDataFromMarketRepo(false); assertNotNull(result); assertTrue(result.isEmpty()); } @@ -306,32 +311,23 @@ void testSyncProductsFirstTime() throws IOException { var mockCommit = mockGHCommitHasSHA1(SHA1_SAMPLE); when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit); when(repoMetaRepo.findByRepoName(anyString())).thenReturn(null); - when(ghAxonIvyProductRepoService.getReadmeAndProductContentsFromTag(any(), any(), anyString())).thenReturn( + when(productContentService.getReadmeAndProductContentsFromVersion(any(), anyString(), anyString(), + any(), anyString())).thenReturn( mockReadmeProductContent()); - when(gitHubService.getRepository(any())).thenReturn(ghRepository); - - GHTag mockTag = mock(GHTag.class); - GHCommit mockGHCommit = mock(GHCommit.class); - when(mockTag.getName()).thenReturn(MOCK_TAG_FROM_RELEASED_VERSION); - when(mockTag.getCommit()).thenReturn(mockGHCommit); - when(mockGHCommit.getCommitDate()).thenReturn(new Date()); - - when(gitHubService.getRepositoryTags(anyString())).thenReturn(List.of(mockTag)); Map> mockGHContentMap = new HashMap<>(); mockGHContentMap.put(SAMPLE_PRODUCT_ID, mockMetaJsonAndLogoList()); when(marketRepoService.fetchAllMarketItems()).thenReturn(mockGHContentMap); when(productModuleContentRepo.saveAll(anyList())).thenReturn(List.of(mockReadmeProductContent())); - when(imageService.mappingImageFromGHContent(any(), any(), anyBoolean())) - .thenReturn(GHAxonIvyProductRepoServiceImplTest.mockImage()); + when(imageService.mappingImageFromGHContent(any(), any())).thenReturn(getMockImage()); when(productRepo.save(any(Product.class))).thenReturn(new Product()); // Executes - productService.syncLatestDataFromMarketRepo(); + productService.syncLatestDataFromMarketRepo(false); verify(productModuleContentRepo).saveAll(argumentCaptorProductModuleContents.capture()); verify(productRepo).save(argumentCaptor.capture()); - assertEquals(1, argumentCaptorProductModuleContents.getValue().size()); + assertEquals(7, argumentCaptorProductModuleContents.getValue().size()); assertThat(argumentCaptorProductModuleContents.getValue().get(0).getId()) .isEqualTo(mockReadmeProductContent().getId()); } @@ -352,49 +348,48 @@ void testSyncProductsFirstTimeWithOutSourceUrl() throws IOException { List mockMetaJsonAndLogoList = new ArrayList<>(List.of(mockContent, mockContentLogo)); mockGHContentMap.put(SAMPLE_PRODUCT_ID, mockMetaJsonAndLogoList); when(marketRepoService.fetchAllMarketItems()).thenReturn(mockGHContentMap); - when(imageService.mappingImageFromGHContent(any(), any(), anyBoolean())).thenReturn( - GHAxonIvyProductRepoServiceImplTest.mockImage()); + when(imageService.mappingImageFromGHContent(any(), any())).thenReturn(getMockImage()); when(productRepo.save(any(Product.class))).thenReturn(new Product()); // Executes - productService.syncLatestDataFromMarketRepo(); + productService.syncLatestDataFromMarketRepo(false); verify(productModuleContentRepo).save(argumentCaptorProductModuleContent.capture()); - assertEquals("1.0", argumentCaptorProductModuleContent.getValue().getTag()); + assertEquals("1.0", argumentCaptorProductModuleContent.getValue().getVersion()); } @Test - void testSyncProductsSecondTime() throws IOException { - Product mockProduct = getMockProduct(); - mockProduct.setProductModuleContent(mockReadmeProductContent()); - mockProduct.setRepositoryName("axonivy-market/bpmn-statistic"); - var gitHubRepoMeta = mock(GitHubRepoMeta.class); - when(gitHubRepoMeta.getLastSHA1()).thenReturn(SHA1_SAMPLE); - var mockCommit = mockGHCommitHasSHA1(SHA1_SAMPLE); - when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit); - when(repoMetaRepo.findByRepoName(anyString())).thenReturn(gitHubRepoMeta); - - when(productRepo.findAll()).thenReturn(List.of(mockProduct)); - GHTag mockTag = mock(GHTag.class); - when(mockTag.getName()).thenReturn(MOCK_TAG_FROM_RELEASED_VERSION); - - GHTag mockTag2 = mock(GHTag.class); - when(mockTag2.getName()).thenReturn("v10.0.3"); - when(gitHubService.getRepositoryTags(anyString())).thenReturn(Arrays.asList(mockTag, mockTag2)); - - ProductModuleContent mockReturnProductContent = mockReadmeProductContent(); - mockReturnProductContent.setTag("v10.0.3"); - - when(ghAxonIvyProductRepoService.getReadmeAndProductContentsFromTag(any(), any(), anyString())) - .thenReturn(mockReturnProductContent); - when(productModuleContentRepo.saveAll(anyList())) - .thenReturn(List.of(mockReadmeProductContent(), mockReturnProductContent)); - - // Executes - productService.syncLatestDataFromMarketRepo(); - - verify(productModuleContentRepo).saveAll(argumentCaptorProductModuleContents.capture()); - verify(productRepo).save(argumentCaptor.capture()); - assertThat(argumentCaptor.getValue().getProductModuleContent().getId()) - .isEqualTo(mockReadmeProductContent().getId()); + void testSyncProductsSecondTime() { + try (MockedStatic mockUtils = Mockito.mockStatic(MavenUtils.class)) { + Product mockProduct = getMockProduct(); + mockProduct.setProductModuleContent(mockReadmeProductContent()); + mockProduct.setRepositoryName(MOCK_PRODUCT_REPOSITORY_NAME); + HashMap names = new HashMap<>(); + names.put(ProductJsonConstants.EN_LANGUAGE, MOCK_PRODUCT_NAME); + mockProduct.setNames(names); + var gitHubRepoMeta = mock(GitHubRepoMeta.class); + when(gitHubRepoMeta.getLastSHA1()).thenReturn(SHA1_SAMPLE); + var mockCommit = mockGHCommitHasSHA1(SHA1_SAMPLE); + when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit); + when(repoMetaRepo.findByRepoName(anyString())).thenReturn(gitHubRepoMeta); + + when(productRepo.findAll()).thenReturn(List.of(mockProduct)); + + ProductModuleContent mockReturnProductContent = mockReadmeProductContent(); + mockReturnProductContent.setVersion(MOCK_RELEASED_VERSION); + + when(productContentService.getReadmeAndProductContentsFromVersion(any(), anyString(), anyString(), any(), + anyString())).thenReturn(mockReturnProductContent); + when(productModuleContentRepo.saveAll(anyList())) + .thenReturn(List.of(mockReadmeProductContent(), mockReturnProductContent)); + mockUtils.when(() -> MavenUtils.getMetadataContentFromUrl(any())).thenReturn(getMockMetadataContent()); + when(MavenUtils.buildDownloadUrl(any(), any(), any(), any(), any(), any())).thenReturn(MOCK_DOWNLOAD_URL); + // Executes + productService.syncLatestDataFromMarketRepo(false); + + verify(productModuleContentRepo).saveAll(argumentCaptorProductModuleContents.capture()); + verify(productRepo).save(argumentCaptor.capture()); + assertThat(argumentCaptor.getValue().getProductModuleContent().getId()) + .isEqualTo(mockReadmeProductContent().getId()); + } } @Test @@ -406,7 +401,7 @@ void testNothingToSync() { when(repoMetaRepo.findByRepoName(anyString())).thenReturn(gitHubRepoMeta); // Executes - var result = productService.syncLatestDataFromMarketRepo(); + var result = productService.syncLatestDataFromMarketRepo(false); assertNotNull(result); assertTrue(result.isEmpty()); } @@ -423,7 +418,7 @@ void testSyncNullProductModuleContent() { when(productRepo.save(any(Product.class))).thenReturn(new Product()); // Executes - productService.syncLatestDataFromMarketRepo(); + productService.syncLatestDataFromMarketRepo(false); verify(productRepo).save(argumentCaptor.capture()); assertThat(argumentCaptor.getValue().getProductModuleContent()).isNull(); @@ -449,7 +444,7 @@ void testFetchProductDetail() { Product mockProduct = getMockProduct(); when(mavenArtifactVersionRepo.findById(MOCK_PRODUCT_ID)).thenReturn( Optional.ofNullable(mockMavenArtifactVersion)); - when(productRepo.getProductByIdWithTagOrVersion(MOCK_PRODUCT_ID, MOCK_TAG_FROM_SNAPSHOT_VERSION)).thenReturn(null); + when(productRepo.getProductByIdAndVersion(MOCK_PRODUCT_ID, MOCK_SNAPSHOT_VERSION)).thenReturn(null); mockProduct.setSynchronizedInstallationCount(true); Product result = productService.fetchProductDetail(MOCK_PRODUCT_ID, true); assertNull(result); @@ -459,28 +454,38 @@ void testFetchProductDetail() { void testGetProductByIdWithNewestReleaseVersion() { MavenArtifactVersion mockMavenArtifactVersion = getMockMavenArtifactVersionWithData(); Product mockProduct = getMockProduct(); - when(mavenArtifactVersionRepo.findById(MOCK_PRODUCT_ID)).thenReturn( - Optional.ofNullable(mockMavenArtifactVersion)); - when(productRepo.getProductByIdWithTagOrVersion(MOCK_PRODUCT_ID, MOCK_TAG_FROM_SNAPSHOT_VERSION)).thenReturn(mockProduct); - Product result = productService.getProductByIdWithNewestReleaseVersion(MOCK_PRODUCT_ID, true); - assertEquals(mockProduct, result); - when(mavenArtifactVersionRepo.findById(MOCK_PRODUCT_ID)).thenReturn(Optional.ofNullable(null)); - when(productRepo.getReleasedVersionsById(MOCK_PRODUCT_ID)).thenReturn(List.of(MOCK_SNAPSHOT_VERSION)); - when(productRepo.getProductByIdWithTagOrVersion(MOCK_PRODUCT_ID, MOCK_SNAPSHOT_VERSION)).thenReturn(mockProduct); - result = productService.getProductByIdWithNewestReleaseVersion(MOCK_PRODUCT_ID, true); - assertEquals(mockProduct, result); + + try (MockedStatic mockUtils = Mockito.mockStatic(MavenUtils.class)) { + mockUtils.when(() -> mavenArtifactVersionRepo.findById(MOCK_PRODUCT_ID)).thenReturn( + Optional.of(mockMavenArtifactVersion)); + when(MavenUtils.getAllExistingVersions(mockMavenArtifactVersion, true, StringUtils.EMPTY)) + .thenReturn(List.of(MOCK_SNAPSHOT_VERSION)); + + when(productRepo.getProductByIdAndVersion(MOCK_PRODUCT_ID, MOCK_SNAPSHOT_VERSION)).thenReturn(mockProduct); + when(productJsonContentRepo.findByProductIdAndVersion(MOCK_PRODUCT_ID, MOCK_SNAPSHOT_VERSION)) + .thenReturn(List.of(getMockProductJsonContentContainMavenDropins())); + + Product result = productService.getProductByIdWithNewestReleaseVersion(MOCK_PRODUCT_ID, true); + assertEquals(mockProduct, result); + + when(mavenArtifactVersionRepo.findById(MOCK_PRODUCT_ID)).thenReturn(Optional.empty()); + when(productRepo.getReleasedVersionsById(MOCK_PRODUCT_ID)).thenReturn(List.of(MOCK_SNAPSHOT_VERSION)); + when(productRepo.getProductByIdAndVersion(MOCK_PRODUCT_ID, MOCK_SNAPSHOT_VERSION)).thenReturn(mockProduct); + result = productService.getProductByIdWithNewestReleaseVersion(MOCK_PRODUCT_ID, true); + assertEquals(mockProduct, result); + } } @Test void testFetchProductDetailByIdAndVersion() { Product mockProduct = mockResultReturn.getContent().get(0); - when(productRepo.getProductByIdWithTagOrVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION)).thenReturn(mockProduct); + when(productRepo.getProductByIdAndVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION)).thenReturn(mockProduct); Product result = productService.fetchProductDetailByIdAndVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); assertEquals(mockProduct, result); - verify(productRepo).getProductByIdWithTagOrVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); + verify(productRepo).getProductByIdAndVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); } @Test @@ -490,21 +495,21 @@ void testFetchBestMatchProductDetailByIdAndVersion() { Metadata mockMetadata = getMockMetadataWithVersions(); mockMetadata.setArtifactId(MOCK_PRODUCT_ARTIFACT_ID); when(metadataRepo.findByProductId(MOCK_PRODUCT_ID)).thenReturn(List.of(mockMetadata)); - when(productRepo.getProductByIdWithTagOrVersion(MOCK_PRODUCT_ID,MOCK_TAG_FROM_RELEASED_VERSION)).thenReturn(mockProduct); + when(productRepo.getProductByIdAndVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION)).thenReturn(mockProduct); Product result = productService.fetchBestMatchProductDetail(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); assertEquals(mockProduct, result); } @Test - void testGetCompatibilityFromNumericTag() { + void testGetCompatibilityFromNumericVersion() { - String result = productService.getCompatibilityFromOldestTag("1.0.0"); + String result = productService.getCompatibilityFromOldestVersion("1.0.0"); assertEquals("1.0+", result); - result = productService.getCompatibilityFromOldestTag("8"); + result = productService.getCompatibilityFromOldestVersion("8"); assertEquals("8.0+", result); - result = productService.getCompatibilityFromOldestTag("11.2"); + result = productService.getCompatibilityFromOldestVersion("11.2"); assertEquals("11.2+", result); } @@ -585,14 +590,6 @@ void testCreateOrder() { assertEquals(SortOption.ALPHABETICALLY.getCode("en"), order.getProperty()); } - @Test - void testClearAllProducts() { - productService.clearAllProducts(); - - verify(repoMetaRepo).deleteAll(); - verify(productRepo).deleteAll(); - } - private void mockMarketRepoMetaStatus() { var mockMarketRepoMeta = new GitHubRepoMeta(); mockMarketRepoMeta.setRepoURL(GitHubConstants.AXONIVY_MARKETPLACE_REPO_NAME); @@ -622,8 +619,8 @@ private GHContent mockGHContentAsLogo() { private ProductModuleContent mockReadmeProductContent() { ProductModuleContent productModuleContent = new ProductModuleContent(); - productModuleContent.setId(MOCK_PRODUCT_ID_WITH_TAG); - productModuleContent.setTag(MOCK_TAG_FROM_RELEASED_VERSION); + productModuleContent.setId(MOCK_PRODUCT_ID_WITH_VERSION); + productModuleContent.setVersion(MOCK_RELEASED_VERSION); productModuleContent.setName(MOCK_PRODUCT_NAME); Map description = new HashMap<>(); description.put(Language.EN.getValue(), "testDescription"); @@ -646,7 +643,7 @@ void testUpdateNewLogoFromGitHub_removeOldLogo() throws IOException { when(marketRepoService.fetchMarketItemsBySHA1Range(any(), any())).thenReturn(List.of(mockGitHubFile)); // Executes - var result = productService.syncLatestDataFromMarketRepo(); + var result = productService.syncLatestDataFromMarketRepo(false); assertNotNull(result); assertTrue(result.isEmpty()); @@ -654,10 +651,9 @@ void testUpdateNewLogoFromGitHub_removeOldLogo() throws IOException { when(mockCommit.getSHA1()).thenReturn(UUID.randomUUID().toString()); mockGitHubFile.setStatus(FileStatus.REMOVED); when(marketRepoService.fetchMarketItemsBySHA1Range(any(), any())).thenReturn(List.of(mockGitHubFile)); - when(imageRepo.findByImageUrlEndsWithIgnoreCase(anyString())) - .thenReturn(List.of(GHAxonIvyProductRepoServiceImplTest.mockImage())); + when(imageRepo.findByImageUrlEndsWithIgnoreCase(anyString())).thenReturn(List.of(getMockImage())); // Executes - result = productService.syncLatestDataFromMarketRepo(); + result = productService.syncLatestDataFromMarketRepo(false); verify(productRepo).deleteById(anyString()); verify(imageRepo).deleteAllByProductId(anyString()); @@ -682,7 +678,7 @@ void testUpdateNewLogoFromGitHub_ModifyLogo() throws IOException { when(productRepo.findByMarketDirectory(anyString())).thenReturn(getMockProducts()); // Executes - var result = productService.syncLatestDataFromMarketRepo(); + var result = productService.syncLatestDataFromMarketRepo(false); assertNotNull(result); assertFalse(result.isEmpty()); @@ -733,7 +729,7 @@ void testSyncProductsAsUpdateMetaJSONFromGitHub_AddVendorLogo() throws IOExcepti when(productRepo.save(any(Product.class))).thenReturn(new Product()); // Executes - var result = productService.syncLatestDataFromMarketRepo(); + var result = productService.syncLatestDataFromMarketRepo(false); assertNotNull(result); assertTrue(result.isEmpty()); } diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/VersionServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/VersionServiceImplTest.java index 8bc7f8f5e..30b81050e 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/VersionServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/VersionServiceImplTest.java @@ -6,7 +6,6 @@ import com.axonivy.market.constants.MavenConstants; import com.axonivy.market.entity.MavenArtifactVersion; import com.axonivy.market.entity.Metadata; -import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductJsonContent; import com.axonivy.market.enums.DevelopmentVersion; import com.axonivy.market.github.service.GHAxonIvyProductRepoService; @@ -15,7 +14,6 @@ import com.axonivy.market.repository.MavenArtifactVersionRepository; import com.axonivy.market.repository.MetadataRepository; import com.axonivy.market.repository.ProductJsonContentRepository; -import com.axonivy.market.repository.ProductModuleContentRepository; import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.util.MavenUtils; import org.apache.commons.lang3.ObjectUtils; @@ -59,9 +57,6 @@ class VersionServiceImplTest extends BaseSetup { @Mock private ProductJsonContentRepository productJsonContentRepository; - @Mock - private ProductModuleContentRepository productModuleContentRepository; - @Mock private MetadataRepository metadataRepo; @@ -92,12 +87,12 @@ void testGetArtifactsAndVersionToDisplay() { void testGetMavenArtifactsFromProductJsonByVersion() { when(productJsonContentRepository.findByProductIdAndVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION)).thenReturn( Collections.emptyList()); - List results = versionService.getMavenArtifactsFromProductJsonByTag(MOCK_RELEASED_VERSION, + List results = versionService.getMavenArtifactsFromProductJsonByVersion(MOCK_RELEASED_VERSION, MOCK_PRODUCT_ID); Assertions.assertTrue(CollectionUtils.isEmpty(results)); when(productJsonContentRepository.findByProductIdAndVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION)).thenReturn( List.of(getMockProductJsonContent())); - results = versionService.getMavenArtifactsFromProductJsonByTag(MOCK_RELEASED_VERSION, MOCK_PRODUCT_ID); + results = versionService.getMavenArtifactsFromProductJsonByVersion(MOCK_RELEASED_VERSION, MOCK_PRODUCT_ID); Assertions.assertEquals(2, results.size()); } @@ -156,7 +151,8 @@ void testGetProductJsonContentByIdAndVersion() { mockProductJsonContent.setName(MOCK_PRODUCT_NAME); Mockito.when(productJsonContentRepository.findByProductIdAndVersion(anyString(), anyString())) .thenReturn(List.of(mockProductJsonContent)); - Map result = versionService.getProductJsonContentByIdAndTag(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); + Map result = versionService.getProductJsonContentByIdAndVersion(MOCK_PRODUCT_ID, + MOCK_RELEASED_VERSION); Assertions.assertEquals(MOCK_PRODUCT_NAME, result.get("name")); } @@ -164,21 +160,11 @@ void testGetProductJsonContentByIdAndVersion() { void testGetProductJsonContentByIdAndVersion_noResult() { Mockito.when(productJsonContentRepository.findByProductIdAndVersion(anyString(), anyString())).thenReturn( Collections.emptyList()); - Map result = versionService.getProductJsonContentByIdAndTag(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); + Map result = versionService.getProductJsonContentByIdAndVersion(MOCK_PRODUCT_ID, + MOCK_RELEASED_VERSION); Assertions.assertEquals(new HashMap<>(), result); } - @Test - void testGetPersistedVersions() { - Assertions.assertTrue(CollectionUtils.isEmpty(versionService.getPersistedVersions(MOCK_PRODUCT_ID))); - Product mocProduct = new Product(); - mocProduct.setId(MOCK_PRODUCT_ID); - mocProduct.setReleasedVersions(List.of(MOCK_RELEASED_VERSION)); - when(productRepository.findById(MOCK_PRODUCT_ID)).thenReturn(Optional.of(mocProduct)); - Assertions.assertTrue(ObjectUtils.isNotEmpty(versionService.getPersistedVersions(MOCK_PRODUCT_ID))); - Assertions.assertEquals(MOCK_RELEASED_VERSION, versionService.getPersistedVersions(MOCK_PRODUCT_ID).get(0)); - } - @Test void testGetAllExistingVersions() { MavenArtifactVersion mockMavenArtifactVersion = new MavenArtifactVersion(); diff --git a/marketplace-service/src/test/java/com/axonivy/market/util/GitHubUtilsTest.java b/marketplace-service/src/test/java/com/axonivy/market/util/GitHubUtilsTest.java index 80f413e27..d6276d841 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/util/GitHubUtilsTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/util/GitHubUtilsTest.java @@ -1,7 +1,6 @@ package com.axonivy.market.util; import com.axonivy.market.BaseSetup; -import com.axonivy.market.enums.NonStandardProduct; import com.axonivy.market.github.util.GitHubUtils; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Assertions; @@ -14,7 +13,6 @@ @ExtendWith(MockitoExtension.class) class GitHubUtilsTest extends BaseSetup { - private static final String JIRA_CONNECTOR = "Jira Connector"; @Test void testConvertArtifactIdToName() { @@ -31,69 +29,6 @@ void testConvertArtifactIdToName() { Assertions.assertEquals(StringUtils.EMPTY, result); } - @Test - void testBuildProductJsonFilePath() { - String result = GitHubUtils.getNonStandardProductFilePath(NonStandardProduct.PORTAL.getId()); - Assertions.assertEquals("AxonIvyPortal/portal-product", result); - - result = GitHubUtils.getNonStandardProductFilePath(NonStandardProduct.CONNECTIVITY_FEATURE.getId()); - Assertions.assertEquals("connectivity/connectivity-demos-product", result); - - result = GitHubUtils.getNonStandardProductFilePath(NonStandardProduct.ERROR_HANDLING.getId()); - Assertions.assertEquals("error-handling/error-handling-demos-product", result); - - result = GitHubUtils.getNonStandardProductFilePath(NonStandardProduct.WORKFLOW_DEMO.getId()); - Assertions.assertEquals("workflow/workflow-demos-product", result); - - result = GitHubUtils.getNonStandardProductFilePath(NonStandardProduct.MICROSOFT_365.getId()); - Assertions.assertEquals("msgraph-connector-product/products/msgraph-connector", result); - - result = GitHubUtils.getNonStandardProductFilePath(NonStandardProduct.MICROSOFT_CALENDAR.getId()); - Assertions.assertEquals("msgraph-connector-product/products/msgraph-calendar", result); - - result = GitHubUtils.getNonStandardProductFilePath(NonStandardProduct.MICROSOFT_TEAMS.getId()); - Assertions.assertEquals("msgraph-connector-product/products/msgraph-chat", result); - - result = GitHubUtils.getNonStandardProductFilePath(NonStandardProduct.MICROSOFT_MAIL.getId()); - Assertions.assertEquals("msgraph-connector-product/products/msgraph-mail", result); - - result = GitHubUtils.getNonStandardProductFilePath(NonStandardProduct.MICROSOFT_TODO.getId()); - Assertions.assertEquals("msgraph-connector-product/products/msgraph-todo", result); - - result = GitHubUtils.getNonStandardProductFilePath(NonStandardProduct.HTML_DIALOG_DEMO.getId()); - Assertions.assertEquals("html-dialog/html-dialog-demos-product", result); - - result = GitHubUtils.getNonStandardProductFilePath(NonStandardProduct.RULE_ENGINE_DEMOS.getId()); - Assertions.assertEquals("rule-engine/rule-engine-demos-product", result); - - result = GitHubUtils.getNonStandardProductFilePath(NonStandardProduct.OPENAI_CONNECTOR.getId()); - Assertions.assertEquals("openai-connector-product", result); - - result = GitHubUtils.getNonStandardProductFilePath(NonStandardProduct.OPENAI_ASSISTANT.getId()); - Assertions.assertEquals("openai-assistant-product", result); - } - - @Test - void testGetNonStandardImageFolder() { - String result = GitHubUtils.getNonStandardImageFolder(NonStandardProduct.EXCEL_IMPORTER.getId()); - Assertions.assertEquals("doc", result); - - result = GitHubUtils.getNonStandardImageFolder(NonStandardProduct.EXPRESS_IMPORTER.getId()); - Assertions.assertEquals("img", result); - - result = GitHubUtils.getNonStandardImageFolder(NonStandardProduct.DEEPL_CONNECTOR.getId()); - Assertions.assertEquals("img", result); - - result = GitHubUtils.getNonStandardImageFolder(NonStandardProduct.GRAPHQL_DEMO.getId()); - Assertions.assertEquals("assets", result); - - result = GitHubUtils.getNonStandardImageFolder(NonStandardProduct.OPENAI_ASSISTANT.getId()); - Assertions.assertEquals("docs", result); - - result = GitHubUtils.getNonStandardImageFolder(JIRA_CONNECTOR); - Assertions.assertEquals("images", result); - } - @Test void testSortMetaJsonFirst() { int result = GitHubUtils.sortMetaJsonFirst(META_FILE, LOGO_FILE); diff --git a/marketplace-service/src/test/java/com/axonivy/market/util/MavenUtilsTest.java b/marketplace-service/src/test/java/com/axonivy/market/util/MavenUtilsTest.java index bbda9f5bf..a89c582f8 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/util/MavenUtilsTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/util/MavenUtilsTest.java @@ -186,13 +186,6 @@ void testExtractMetaDataFromArchivedArtifacts() { results.iterator().next().getUrl()); } - @Test - void testBuildDownloadUrl() { - Metadata metadata = buildMocKMetadata(); - Assertions.assertEquals(MOCK_SNAPSHOT_DOWNLOAD_URL, - MavenUtils.buildDownloadUrl(metadata, MOCK_SNAPSHOT_VERSION)); - } - @Test void testGetMetadataContent() { Assertions.assertEquals(StringUtils.EMPTY, MavenUtils.getMetadataContentFromUrl("octopus.com")); diff --git a/marketplace-service/src/test/java/com/axonivy/market/util/MetadataReaderUtilsTest.java b/marketplace-service/src/test/java/com/axonivy/market/util/MetadataReaderUtilsTest.java index b5591d299..184068264 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/util/MetadataReaderUtilsTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/util/MetadataReaderUtilsTest.java @@ -1,17 +1,24 @@ package com.axonivy.market.util; import com.axonivy.market.BaseSetup; +import com.axonivy.market.bo.Artifact; import com.axonivy.market.constants.MavenConstants; import com.axonivy.market.entity.Metadata; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + @ExtendWith(MockitoExtension.class) class MetadataReaderUtilsTest extends BaseSetup { private static final String INVALID_METADATA = ""; @@ -30,9 +37,9 @@ void testUpdateMetadataFromReleasesMavenXML() { LocalDateTime expectedLastUpdated = LocalDateTime.parse("20230924010101", DateTimeFormatter.ofPattern(MavenConstants.DATE_TIME_FORMAT)); - Assertions.assertEquals(MOCK_SPRINT_RELEASED_VERSION, modifiedMetadata.getLatest()); - Assertions.assertEquals(MOCK_RELEASED_VERSION, modifiedMetadata.getRelease()); - Assertions.assertEquals(expectedLastUpdated, modifiedMetadata.getLastUpdated()); + assertEquals(MOCK_SPRINT_RELEASED_VERSION, modifiedMetadata.getLatest()); + assertEquals(MOCK_RELEASED_VERSION, modifiedMetadata.getRelease()); + assertEquals(expectedLastUpdated, modifiedMetadata.getLastUpdated()); } @Test @@ -46,6 +53,27 @@ void testUpdateMetadataFromInvalidSnapshotMavenXML() { @Test void testUpdateMetadataFromSnapshotXml() { MetadataReaderUtils.updateMetadataFromMavenXML(getMockSnapShotMetadataContent(), metadata, true); - Assertions.assertEquals("8.0.5-20221011.124215-170", metadata.getSnapshotVersionValue()); + assertEquals("8.0.5-20221011.124215-170", metadata.getSnapshotVersionValue()); + } + + @Test + void testGetSnapshotVersionValue() { + try (MockedStatic mockUtils = Mockito.mockStatic(MavenUtils.class)) { + Artifact mockArtifact = mock(Artifact.class); + + // Mock Artifact properties + when(mockArtifact.getRepoUrl()).thenReturn("http://example.com/maven"); + when(mockArtifact.getGroupId()).thenReturn(MOCK_GROUP_ID); + when(mockArtifact.getArtifactId()).thenReturn(MOCK_ARTIFACT_ID); + + String mockMetadataUrl = "http://example.com/maven/metadata.xml"; + + mockUtils.when(() -> MavenUtils.buildSnapshotMetadataUrlFromArtifactInfo("http://example.com/maven", + MOCK_GROUP_ID, MOCK_ARTIFACT_ID, MOCK_SNAPSHOT_VERSION)).thenReturn(mockMetadataUrl); + when(MavenUtils.getMetadataContentFromUrl(mockMetadataUrl)).thenReturn(getMockSnapShotMetadataContent()); + + String snapshotVersionValue = MetadataReaderUtils.getSnapshotVersionValue(MOCK_SNAPSHOT_VERSION, mockArtifact); + assertEquals("8.0.5-20221011.124215-170", snapshotVersionValue); + } } } \ No newline at end of file diff --git a/marketplace-service/src/test/java/com/axonivy/market/util/VersionUtilsTest.java b/marketplace-service/src/test/java/com/axonivy/market/util/VersionUtilsTest.java index 8a2c2bfbe..84c626d29 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/util/VersionUtilsTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/util/VersionUtilsTest.java @@ -1,20 +1,12 @@ package com.axonivy.market.util; import com.axonivy.market.BaseSetup; -import com.axonivy.market.entity.Product; -import com.axonivy.market.enums.NonStandardProduct; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.kohsuke.github.GHTag; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.util.CollectionUtils; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; @@ -53,15 +45,6 @@ void testIsMatchWithDesignerVersion() { Assertions.assertFalse(VersionUtils.isMatchWithDesignerVersion(targetVersion, MOCK_RELEASED_VERSION)); } - @Test - void testConvertVersionToTag() { - Assertions.assertEquals(StringUtils.EMPTY, VersionUtils.convertVersionToTag(StringUtils.EMPTY, StringUtils.EMPTY)); - Assertions.assertEquals(MOCK_RELEASED_VERSION, - VersionUtils.convertVersionToTag(NonStandardProduct.PORTAL.getId(), MOCK_RELEASED_VERSION)); - Assertions.assertEquals(MOCK_TAG_FROM_RELEASED_VERSION, - VersionUtils.convertVersionToTag(NonStandardProduct.GRAPHQL_DEMO.getId(), MOCK_RELEASED_VERSION)); - } - @Test void testGetVersionsToDisplay() { ArrayList versionFromArtifact = new ArrayList<>(); @@ -110,45 +93,6 @@ void testGetBestMatchVersion() { Assertions.assertEquals("10.0.21", VersionUtils.getBestMatchVersion(releasedVersions, "10.0.16")); } - @Test - void testConvertTagToVersion() { - Assertions.assertEquals(MOCK_RELEASED_VERSION, VersionUtils.convertTagToVersion(MOCK_RELEASED_VERSION)); - Assertions.assertEquals(MOCK_RELEASED_VERSION, VersionUtils.convertTagToVersion(MOCK_TAG_FROM_RELEASED_VERSION)); - Assertions.assertEquals(StringUtils.EMPTY, VersionUtils.convertTagToVersion(StringUtils.EMPTY)); - } - - @Test - void testConvertTagsToVersions() { - List results = VersionUtils.convertTagsToVersions(List.of("10.0.1", MOCK_TAG_FROM_RELEASED_VERSION)); - Assertions.assertEquals(2, results.size()); - Assertions.assertEquals("10.0.1", results.get(0)); - Assertions.assertEquals(MOCK_RELEASED_VERSION, results.get(1)); - } - - @Test - void testGetOldestVersionWithEmptyTags() { - List tags = List.of(); - String oldestTag = VersionUtils.getOldestVersion(tags); - Assertions.assertEquals(StringUtils.EMPTY, oldestTag); - } - - @Test - void testGetOldestVersionWithNullTags() { - String oldestTag = VersionUtils.getOldestVersion(null); - Assertions.assertEquals(StringUtils.EMPTY, oldestTag); - } - - @Test - void testGetOldestVersionWithNonNumericCharacters() { - GHTag tag1 = Mockito.mock(GHTag.class); - GHTag tag2 = Mockito.mock(GHTag.class); - Mockito.when(tag1.getName()).thenReturn("v1.0"); - Mockito.when(tag2.getName()).thenReturn("2.1"); - List tags = Arrays.asList(tag1, tag2); - String oldestTag = VersionUtils.getOldestVersion(tags); - Assertions.assertEquals("1.0", oldestTag); - } - @Test void testRemoveSyncedVersionsFromReleasedVersions() { Set syncVersion = Set.of("1.0.0"); @@ -163,15 +107,4 @@ void testRemoveSyncedVersionsFromReleasedVersions() { Assertions.assertEquals(1, result.size()); Assertions.assertEquals("2.0.0", result.get(0)); } - - @Test - void testGetReleaseTagsFromProduct() { - List result = VersionUtils.getReleaseTagsFromProduct(null); - Assertions.assertNotNull(result); - Assertions.assertTrue(CollectionUtils.isEmpty(result)); - Product mockProduct = Product.builder().id("portal").releasedVersions(List.of("1.0.0")).build(); - result = VersionUtils.getReleaseTagsFromProduct(mockProduct); - Assertions.assertTrue(ObjectUtils.isNotEmpty(result)); - Assertions.assertEquals("1.0.0", result.get(0)); - } } diff --git a/marketplace-service/src/test/resources/README.md b/marketplace-service/src/test/resources/README.md new file mode 100644 index 000000000..ced1d2e07 --- /dev/null +++ b/marketplace-service/src/test/resources/README.md @@ -0,0 +1,37 @@ +# Employee Onboarding solution + +Axon Ivy’s Employee Onboarding solution provides best practice guidance to HR +specialists, ensuring that provisioning and all other aspects of the employee +onboarding process are handled quickly and efficiently. In addition, HR managers +and their superiors can capture all the information required to automatically +set up employees in HR systems. The solution: + +- ensures timely and personalized communication with new employees from their + first day on the job (and even before) +- guarantees no tasks are left undone +- helps new hires become productive much faster +- makes sure that required materials are procured and ready for employees in + advance +- increases employee retention rates +- contributes to a positive company image + +### Summary + +You only have one chance to make a first impression. Failing to deliver a smooth +employee onboarding experience can contribute to high turnover rates and +inefficiency. A structured onboarding process ensures that employees have all +the necessary materials and information available when they start work, which +increases employee satisfaction. Axon Ivy’s Employee Onboarding Solution not +only coordinates processes within HR departments, it also automates functions +across external departments and process owners. + +### Information + +- Industry: All Industries +- Compatible Version(s): 8.0.x, 10.0.x + +## Demo + +![Screen 1](screen1.png "Screen 1") + +![Screen 2](screen2.png "Screen 2") \ No newline at end of file diff --git a/marketplace-service/src/test/resources/product-dropins.json b/marketplace-service/src/test/resources/product-dropins.json new file mode 100644 index 000000000..036cbf0fe --- /dev/null +++ b/marketplace-service/src/test/resources/product-dropins.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.axonivy.com/market/10.0.0/product.json", + "installers": [ + { + "id": "maven-dropins", + "data": { + "dependencies": [ + { + "groupId": "com.axonivy.connector.openai", + "artifactId": "openai-assistant", + "version": "${version}" + } + ], + "repositories": [ + { + "id": "maven.axonivy.com", + "url": "https://maven.axonivy.com", + "snapshots": { + "enabled": "true" + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/marketplace-service/src/test/resources/setup.md b/marketplace-service/src/test/resources/setup.md deleted file mode 100644 index 46d23b7f0..000000000 --- a/marketplace-service/src/test/resources/setup.md +++ /dev/null @@ -1,11 +0,0 @@ -## Setup -### Variables -In order to use this product you must configure multiple variables. - -Add the following block to your `config/variables.yaml` file of ours -main Business Project that will make use of this product: - -``` -@variables.yaml@ -``` -![set-redirect](image.png) \ No newline at end of file diff --git a/marketplace-ui/src/app/app.component.html b/marketplace-ui/src/app/app.component.html index 016a69a82..3a65033de 100644 --- a/marketplace-ui/src/app/app.component.html +++ b/marketplace-ui/src/app/app.component.html @@ -18,9 +18,7 @@ @if (!routingQueryParamService.isDesignerEnv()) {
-
- -
+
} diff --git a/marketplace-ui/src/app/app.component.scss b/marketplace-ui/src/app/app.component.scss index 693e97949..cdd509942 100644 --- a/marketplace-ui/src/app/app.component.scss +++ b/marketplace-ui/src/app/app.component.scss @@ -14,6 +14,10 @@ footer { padding: 0 0 56px 0; border-top: 1px solid var(--ivy-secondary-border-color); margin-top: 64.14px; + + @media screen and (max-width: 991px) { + margin-top: 4rem; + } } @media all and (max-width: 992px) { diff --git a/marketplace-ui/src/app/app.routes.ts b/marketplace-ui/src/app/app.routes.ts index 4fc2fc5a8..93f3b9b41 100644 --- a/marketplace-ui/src/app/app.routes.ts +++ b/marketplace-ui/src/app/app.routes.ts @@ -2,15 +2,18 @@ import { Routes } from '@angular/router'; import { GithubCallbackComponent } from './auth/github-callback/github-callback.component'; import { ErrorPageComponent } from './shared/components/error-page/error-page.component'; import { RedirectPageComponent } from './shared/components/redirect-page/redirect-page.component'; +import { ERROR_PAGE } from './shared/constants/common.constant'; export const routes: Routes = [ { path: 'error-page', - component: ErrorPageComponent + component: ErrorPageComponent, + title: ERROR_PAGE }, { path: 'error-page/:id', - component: ErrorPageComponent + component: ErrorPageComponent, + title: ERROR_PAGE }, { path: '', diff --git a/marketplace-ui/src/app/modules/home/home.component.ts b/marketplace-ui/src/app/modules/home/home.component.ts index 915e2b378..115645aad 100644 --- a/marketplace-ui/src/app/modules/home/home.component.ts +++ b/marketplace-ui/src/app/modules/home/home.component.ts @@ -1,11 +1,19 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { ProductComponent } from '../product/product.component'; +import { TranslateService } from '@ngx-translate/core'; +import { Title } from '@angular/platform-browser'; @Component({ selector: 'app-home', standalone: true, imports: [ProductComponent], templateUrl: './home.component.html', - styleUrl: './home.component.scss', + styleUrl: './home.component.scss' }) -export class HomeComponent {} +export class HomeComponent { + translateService = inject(TranslateService); + + constructor(private readonly titleService: Title) { + this.titleService.setTitle(this.translateService.instant('common.branch')); + } +} diff --git a/marketplace-ui/src/app/modules/product/product-card/product-card.component.spec.ts b/marketplace-ui/src/app/modules/product/product-card/product-card.component.spec.ts index 1163d6362..b6e4c0e4d 100644 --- a/marketplace-ui/src/app/modules/product/product-card/product-card.component.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-card/product-card.component.spec.ts @@ -68,7 +68,7 @@ describe('ProductCardComponent', () => { ); }); - it('should display product tag in REST client', () => { + it('should display product version in REST client', () => { component.isShowInRESTClientEditor = true; fixture.detectChanges(); diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.html b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.html index bc78215df..4d70eb386 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.html +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.html @@ -7,11 +7,16 @@ class="rounded-circle img-avatar img-fit-cover" [src]="feedback.userAvatarUrl ?? '/assets/images/misc/avatar-default.png'" alt="Github user avatar" /> -
-

- {{ feedback.username ?? 'Github User' }} +

+
+

+ {{ feedback.username ?? 'Github User' }} +

+ +
+ -
diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.scss b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.scss index ba539acca..624af5135 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.scss +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.scss @@ -87,3 +87,8 @@ $aspect-ratio: math.div(1, 1); line-height: 21px; } +.feedback-time-text { + font-size: 1.2rem; + line-height: 1.452rem; + color: var(--text-feedback-time-color); +} \ No newline at end of file diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.spec.ts index bffc76b70..7ed508a43 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.spec.ts @@ -4,6 +4,8 @@ import { CommonModule } from '@angular/common'; import { StarRatingComponent } from '../../../../../../shared/components/star-rating/star-rating.component'; import { ElementRef } from '@angular/core'; import { Feedback } from '../../../../../../shared/models/feedback.model'; +import { MissingTranslationHandler, TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; +import { httpLoaderFactory } from '../../../../../../core/configs/translate.config'; describe('ProductFeedbackComponent', () => { let component: ProductFeedbackComponent; @@ -21,12 +23,30 @@ describe('ProductFeedbackComponent', () => { await TestBed.configureTestingModule({ imports: [ProductFeedbackComponent, StarRatingComponent, CommonModule], providers: [ - { provide: ElementRef, useValue: mockElementRef } + { provide: ElementRef, useValue: mockElementRef }, + TranslateService ] }).compileComponents(); + + }); beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: httpLoaderFactory, + }, + missingTranslationHandler: { + provide: MissingTranslationHandler, + useValue: { handle: () => 'Translation missing' } + } + }) + ] + }); + fixture = TestBed.createComponent(ProductFeedbackComponent); component = fixture.componentInstance; component.feedback = { diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.ts index f894643c9..b3176734c 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-feedback/product-feedbacks-panel/product-feedback/product-feedback.component.ts @@ -1,12 +1,14 @@ -import { Component, ElementRef, HostListener, Input, signal, ViewChild } from '@angular/core'; +import { Component, ElementRef, HostListener, inject, Input, signal, ViewChild } from '@angular/core'; import { CommonModule } from '@angular/common'; import { StarRatingComponent } from '../../../../../../shared/components/star-rating/star-rating.component'; import { Feedback } from '../../../../../../shared/models/feedback.model'; +import { TimeAgoPipe } from '../../../../../../shared/pipes/time-ago.pipe'; +import { LanguageService } from '../../../../../../core/services/language/language.service'; @Component({ selector: 'app-product-feedback', standalone: true, - imports: [CommonModule, StarRatingComponent], + imports: [CommonModule, StarRatingComponent, TimeAgoPipe], templateUrl: './product-feedback.component.html', styleUrl: './product-feedback.component.scss' }) @@ -16,6 +18,7 @@ export class ProductFeedbackComponent { showToggle = signal(false); isExpanded = signal(false); + languageService = inject(LanguageService); ngAfterViewInit() { this.setShowToggle(); diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.spec.ts index 0ad112e39..c0fd9b7cc 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.spec.ts @@ -10,9 +10,7 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { MOCK_EXTERNAL_DOCUMENT } from '../../../../shared/mocks/mock-data'; const TEST_ID = 'portal'; -const TEST_VERSION = 'v10.0.0'; -const TEST_ACTUAL_VERSION = '10.0.0'; -const TEST_ARTIFACT_ID = 'portal-guide'; +const TEST_VERSION = '10.0.0'; const TEST_ARTIFACT_NAME = 'Portal Guide'; const TEST_DOC_URL = '/market-cache/portal/portal-guide/10.0.0/doc/index.html'; @@ -52,7 +50,7 @@ describe('ProductDetailInformationTabComponent', () => { const changes: SimpleChanges = { selectedVersion: { currentValue: TEST_VERSION, - previousValue: 'v8.0.0', + previousValue: '8.0.0', firstChange: false, isFirstChange: () => false }, @@ -66,7 +64,7 @@ describe('ProductDetailInformationTabComponent', () => { component.ngOnChanges(changes); - expect(productDetailService.getExteralDocumentForProductByVersion).toHaveBeenCalledWith(TEST_ID, TEST_ACTUAL_VERSION); + expect(productDetailService.getExteralDocumentForProductByVersion).toHaveBeenCalledWith(TEST_ID, TEST_VERSION); expect(component.externalDocumentLink).toBe(TEST_DOC_URL); expect(component.displayExternalDocName).toBe(TEST_ARTIFACT_NAME); }); @@ -77,7 +75,7 @@ describe('ProductDetailInformationTabComponent', () => { const changes: SimpleChanges = { selectedVersion: { currentValue: '', - previousValue: 'v8.0.0', + previousValue: '8.0.0', firstChange: false, isFirstChange: () => false }, @@ -97,8 +95,8 @@ describe('ProductDetailInformationTabComponent', () => { }); it('should extract version value correctly', () => { - const versionDisplayName = TEST_VERSION; + const versionDisplayName = "Version 10.0.0"; const extractedValue = component.extractVersionValue(versionDisplayName); - expect(extractedValue).toBe(TEST_ACTUAL_VERSION); + expect(extractedValue).toBe(TEST_VERSION); }); -}); \ No newline at end of file +}); diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts index 07f379f39..3127f16c9 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-information-tab/product-detail-information-tab.component.ts @@ -64,6 +64,6 @@ export class ProductDetailInformationTabComponent implements OnChanges { } extractVersionValue(versionDisplayName: string) { - return versionDisplayName.replace(VERSION.displayPrefix, '').replace(VERSION.tagPrefix, ''); + return versionDisplayName.replace(VERSION.displayPrefix, ''); } } diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.html b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.html index 9b6f9cd3b..2d494492e 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.html +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail-version-action/product-detail-version-action.component.html @@ -1,8 +1,13 @@ @switch (actionType) { @case (ProductDetailActionType.STANDARD) {
- - - + @if (!isMavenDropins) { + + } + @if (isDropDownDisplayed()) {
@@ -34,7 +45,7 @@ 'common.product.detail.download.artifactSelector.label' | translate }} - +
- +
} @case (ProductDetailActionType.DESIGNER_ENV) {
@@ -119,4 +130,4 @@ {{ 'common.product.detail.contactUs.label' | translate }} } -} \ No newline at end of file +} 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 c548ae066..c65095340 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 @@ -52,6 +52,7 @@ export class ProductDetailVersionActionComponent implements AfterViewInit { protected readonly environment = environment; @Output() installationCount = new EventEmitter(); @Input() productId!: string; + @Input() isMavenDropins!: boolean; @Input() actionType!: ProductDetailActionType; @Input() product!: ProductDetail; protected ProductDetailActionType = ProductDetailActionType; 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 29653d343..159c8879a 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 @@ -3,17 +3,17 @@ class="product-detail-container d-flex flex-column justify-content-center p-0">
@@ -75,6 +75,7 @@

[(selectedVersion)]="selectedVersion!" [(metaDataJsonUrl)]="metaProductJsonUrl!" [productId]="productDetail().id" + [isMavenDropins]="productDetail().mavenDropins" [actionType]="productDetailActionType()" (selectedVersionChange)=" loadDetailTabs($event) @@ -152,7 +153,6 @@

- @if (!isEmptyProductContent()) { @for (displayedTab of displayedTabsSignal(); track $index) {
}
} - }
}
diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.scss b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.scss index 797ba3d8f..c870c88f6 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.scss +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.scss @@ -29,10 +29,11 @@ } } -.back-link { +#back-to-homepage-button { font-size: 1.6rem; font-weight: 500; line-height: 120%; + cursor: pointer; } .analysis-container { @@ -111,6 +112,10 @@ border-bottom: 1px solid var(--ivy-border-color); } } + + pre code { + font-size: 1.4rem; + } } .nav-tabs { @@ -124,8 +129,6 @@ .nav-item { gap: 10px; - - a { font-weight: 400; font-size: 22px; 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 08d610244..c2bc04c89 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 @@ -9,7 +9,7 @@ import { TestBed, tick } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; +import { By, Title } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { Viewport } from 'karma-viewport/dist/adapter/viewport'; @@ -38,13 +38,14 @@ describe('ProductDetailComponent', () => { let fixture: ComponentFixture; let routingQueryParamService: jasmine.SpyObj; let languageService: jasmine.SpyObj; + let titleService: Title; beforeEach(async () => { const routingQueryParamServiceSpy = jasmine.createSpyObj( 'RoutingQueryParamService', ['getDesignerVersionFromCookie', 'isDesignerEnv'] ); - + const languageServiceSpy = jasmine.createSpyObj( 'LanguageService', ['selectedLanguage'] @@ -76,7 +77,8 @@ describe('ProductDetailComponent', () => { { provide: LanguageService, useValue: languageServiceSpy - } + }, + Title ] }) .overrideComponent(ProductDetailComponent, { @@ -93,6 +95,8 @@ describe('ProductDetailComponent', () => { languageService = TestBed.inject( LanguageService ) as jasmine.SpyObj; + + titleService = TestBed.inject(Title); }); beforeEach(() => { @@ -107,6 +111,18 @@ describe('ProductDetailComponent', () => { ); }); + it('should have title like the name DE', () => { + languageService.selectedLanguage.and.returnValue( + Language.DE + ); + component.updateWebBrowserTitle(); + fixture.detectChanges(); + + expect(titleService.getTitle()).toEqual( + MOCK_PRODUCT_DETAIL.names[Language.DE] + ); + }); + it('version should display in number', () => { expect(component.selectedVersion).toEqual('Version 10.0.0'); }); @@ -182,7 +198,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, description: { en: 'Test description' } }; - + const selectedLanguage = Language.EN; languageService.selectedLanguage.and.returnValue( @@ -198,7 +214,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, description: { de: 'Test description' } }; - + const selectedLanguage = Language.DE; languageService.selectedLanguage.and.returnValue( @@ -214,7 +230,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, description: { en: 'Test description', de: '' } }; - + const selectedLanguage = Language.DE; languageService.selectedLanguage.and.returnValue( @@ -230,7 +246,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, description: { en: 'Test description'} }; - + const selectedLanguage = Language.DE; languageService.selectedLanguage.and.returnValue( @@ -264,7 +280,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, description: { en: '', de: 'Test description' } }; - + const selectedLanguage = Language.EN; languageService.selectedLanguage.and.returnValue( @@ -296,7 +312,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, description: { en: '', de: '' } }; - + const selectedLanguage = Language.EN; languageService.selectedLanguage.and.returnValue( @@ -312,7 +328,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, description: {} }; - + const selectedLanguage = Language.EN; languageService.selectedLanguage.and.returnValue( @@ -328,7 +344,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, setup: { en: 'Test setup' } }; - + const selectedLanguage = Language.EN; languageService.selectedLanguage.and.returnValue( @@ -344,7 +360,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, setup: { de: 'Test setup' } }; - + const selectedLanguage = Language.DE; languageService.selectedLanguage.and.returnValue( @@ -360,7 +376,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, setup: { en: 'Test setup', de: '' } }; - + const selectedLanguage = Language.DE; languageService.selectedLanguage.and.returnValue( @@ -376,7 +392,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, setup: { en: 'Test setup'} }; - + const selectedLanguage = Language.DE; languageService.selectedLanguage.and.returnValue( @@ -431,7 +447,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, setup: { en: '', de: '' } }; - + const selectedLanguage = Language.EN; languageService.selectedLanguage.and.returnValue( @@ -447,7 +463,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, setup: {} }; - + const selectedLanguage = Language.EN; languageService.selectedLanguage.and.returnValue( @@ -463,7 +479,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, demo: { en: 'Test demo' } }; - + const selectedLanguage = Language.EN; languageService.selectedLanguage.and.returnValue( @@ -479,7 +495,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, demo: { de: 'Test demo' } }; - + const selectedLanguage = Language.DE; languageService.selectedLanguage.and.returnValue( @@ -495,7 +511,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, demo: { en: 'Test demo', de: '' } }; - + const selectedLanguage = Language.DE; languageService.selectedLanguage.and.returnValue( @@ -511,7 +527,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, demo: { en: 'Test demo'} }; - + const selectedLanguage = Language.DE; languageService.selectedLanguage.and.returnValue( @@ -534,7 +550,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, demo: { en: '', de: 'Test demo' } }; - + const selectedLanguage = Language.EN; languageService.selectedLanguage.and.returnValue( @@ -566,7 +582,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, demo: { en: '', de: '' } }; - + const selectedLanguage = Language.EN; languageService.selectedLanguage.and.returnValue( @@ -582,7 +598,7 @@ describe('ProductDetailComponent', () => { ...MOCK_PRODUCT_MODULE_CONTENT, demo: {} }; - + const selectedLanguage = Language.EN; languageService.selectedLanguage.and.returnValue( @@ -707,7 +723,7 @@ describe('ProductDetailComponent', () => { it('should be formated selected version if open in designer', () => { const mockContent: ProductModuleContent = { ...MOCK_PRODUCT_MODULE_CONTENT, - tag: '10.0.11' + version: '10.0.11' }; component.productModuleContent.set(mockContent); routingQueryParamService.isDesignerEnv.and.returnValue(true); 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 ea153175a..afb527116 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 @@ -49,6 +49,8 @@ import { ProductStarRatingNumberComponent } from './product-star-rating-number/p import { DisplayValue } from '../../../shared/models/display-value.model'; import { CookieService } from 'ngx-cookie-service'; import { ROUTER } from '../../../shared/constants/router.constant'; +import { Title } from '@angular/platform-browser'; +import { API_URI } from '../../../shared/constants/api.constant'; export interface DetailTab { activeClass: string; @@ -130,7 +132,7 @@ export class ProductDetailComponent { this.updateDropdownSelection(); } - constructor() { + constructor(private readonly titleService: Title) { this.scrollToTop(); this.resizeObserver = new ResizeObserver(() => { this.updateDropdownSelection(); @@ -156,6 +158,7 @@ export class ProductDetailComponent { this.handleProductContentVersion(); this.updateProductDetailActionType(productDetail); this.logoUrl = productDetail.logoUrl; + this.updateWebBrowserTitle(); }); this.productFeedbackService.initFeedbacks(); @@ -164,6 +167,10 @@ export class ProductDetailComponent { this.updateDropdownSelection(); } + onClickingBackToHomepageButton() { + this.router.navigate([API_URI.APP]); + } + onLogoError() { this.logoUrl = DEFAULT_IMAGE_URL; } @@ -172,9 +179,7 @@ export class ProductDetailComponent { if (this.isEmptyProductContent()) { return; } - this.selectedVersion = VERSION.displayPrefix.concat( - this.convertTagToVersion(this.productModuleContent().tag) - ); + this.selectedVersion = VERSION.displayPrefix.concat(this.productModuleContent().version); } updateProductDetailActionType(productDetail: ProductDetail) { @@ -322,6 +327,7 @@ export class ProductDetailComponent { handleClickOutside(event: MouseEvent) { const formSelect = this.elementRef.nativeElement.querySelector('.form-select'); + if ( formSelect && !formSelect.contains(event.target) && @@ -365,14 +371,15 @@ export class ProductDetailComponent { }); } - convertTagToVersion(tag: string): string { - if (tag !== '' && tag.startsWith(VERSION.tagPrefix)) { - return tag.substring(1); + updateWebBrowserTitle() { + if (this.productDetail().names !== undefined) { + const title = this.productDetail().names[this.languageService.selectedLanguage()]; + this.titleService.setTitle(title); } - return tag; } getDisplayedTabsSignal() { + this.updateWebBrowserTitle(); const displayedTabs: ItemDropdown[] = []; for (const detailTab of this.detailTabs) { if (this.getContent(detailTab.value)) { @@ -400,7 +407,7 @@ export class ProductDetailComponent { productDetail.vendorImage = vendorImage || vendorImageDarkMode; productDetail.vendorImageDarkMode = vendorImageDarkMode || vendorImage; } - + return productDetail; } -} \ No newline at end of file +} diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-installation-count-action/product-installation-count-action.component.html b/marketplace-ui/src/app/modules/product/product-detail/product-installation-count-action/product-installation-count-action.component.html index 98a1a2082..787cdc386 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-installation-count-action/product-installation-count-action.component.html +++ b/marketplace-ui/src/app/modules/product/product-detail/product-installation-count-action/product-installation-count-action.component.html @@ -2,8 +2,5 @@

{{ 'common.product.detail.installation' | translate }}

-

{{this.currentInstallationCount}}

-

- {{ 'common.product.detail.times' | translate }} -

-
\ No newline at end of file +

{{ this.currentInstallationCount }}

+
diff --git a/marketplace-ui/src/app/modules/product/product.component.html b/marketplace-ui/src/app/modules/product/product.component.html index b43d7602d..17e919271 100644 --- a/marketplace-ui/src/app/modules/product/product.component.html +++ b/marketplace-ui/src/app/modules/product/product.component.html @@ -33,7 +33,7 @@

@if (products().length > 0) {
@for (product of products(); track product.id) { -
+
} diff --git a/marketplace-ui/src/app/modules/product/product.component.spec.ts b/marketplace-ui/src/app/modules/product/product.component.spec.ts index f8fa64d33..be2db33b5 100644 --- a/marketplace-ui/src/app/modules/product/product.component.spec.ts +++ b/marketplace-ui/src/app/modules/product/product.component.spec.ts @@ -6,7 +6,7 @@ import { } from '@angular/core/testing'; import { provideHttpClient } from '@angular/common/http'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, provideRouter, Router } from '@angular/router'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { of, Subscription } from 'rxjs'; import { TypeOption } from '../../shared/enums/type-option.enum'; @@ -17,16 +17,17 @@ import { MockProductService } from '../../shared/mocks/mock-services'; import { RoutingQueryParamService } from '../../shared/services/routing.query.param.service'; import { DESIGNER_COOKIE_VARIABLE } from '../../shared/constants/common.constant'; import { ItemDropdown } from '../../shared/models/item-dropdown.model'; - -const router = { - navigate: jasmine.createSpy('navigate') -}; +import { By } from '@angular/platform-browser'; +import { Location } from '@angular/common'; +import { ProductDetailComponent } from './product-detail/product-detail.component'; describe('ProductComponent', () => { let component: ProductComponent; let fixture: ComponentFixture; let mockIntersectionObserver: any; let routingQueryParamService: jasmine.SpyObj; + let location: Location; + let router: Router; beforeAll(() => { mockIntersectionObserver = jasmine.createSpyObj('IntersectionObserver', [ @@ -65,10 +66,6 @@ describe('ProductComponent', () => { await TestBed.configureTestingModule({ imports: [ProductComponent, TranslateModule.forRoot()], providers: [ - { - provide: Router, - useValue: router - }, { provide: ActivatedRoute, useValue: { @@ -81,6 +78,12 @@ describe('ProductComponent', () => { provide: RoutingQueryParamService, useValue: routingQueryParamService }, + provideRouter([ + { + path: ':id', + component: ProductDetailComponent + } + ]), ProductService, TranslateService, provideHttpClient() @@ -93,12 +96,17 @@ describe('ProductComponent', () => { } }) .compileComponents(); + + location = TestBed.inject(Location); + router = TestBed.inject(Router); + routingQueryParamService = TestBed.inject( RoutingQueryParamService ) as jasmine.SpyObj; fixture = TestBed.createComponent(ProductComponent); component = fixture.componentInstance; + fixture.detectChanges(); }); @@ -196,6 +204,10 @@ describe('ProductComponent', () => { }); it('should set isRESTClient true based on query params and designer environment', () => { + component.route.queryParams = of({ + [DESIGNER_COOKIE_VARIABLE.restClientParamName]: 'resultsOnly', + }); + routingQueryParamService.isDesignerEnv.and.returnValue(true); const fixtureTest = TestBed.createComponent(ProductComponent); component = fixtureTest.componentInstance; @@ -215,4 +227,22 @@ describe('ProductComponent', () => { const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('.row col-md-12 mt-8')).toBeNull(); }); + + it('should navigate to product detail page when clicking on a product card', async () => { + routingQueryParamService.isDesignerEnv.and.returnValue(false); + const fixtureTest = TestBed.createComponent(ProductComponent); + component = fixtureTest.componentInstance; + + expect(component.isRESTClient()).toBeFalse(); + + const productName = 'amazon-comprehend'; + + const productCardComponent = fixture.debugElement.query( + By.css('.product-card') + ).nativeElement as HTMLDivElement; + + productCardComponent.click(); + await router.navigate([productName]); + expect(location.path()).toBe('/amazon-comprehend'); + }); }); diff --git a/marketplace-ui/src/app/modules/product/product.component.ts b/marketplace-ui/src/app/modules/product/product.component.ts index 37a55a885..46a8eac23 100644 --- a/marketplace-ui/src/app/modules/product/product.component.ts +++ b/marketplace-ui/src/app/modules/product/product.component.ts @@ -115,7 +115,10 @@ export class ProductComponent implements AfterViewInit, OnDestroy { } viewProductDetail(productId: string, _productTag: string) { - window.location.href = `/${productId}`; + if(this.isRESTClient()) { + window.location.href = `/${productId}`; + } + this.router.navigate([`/${productId}`]); } onFilterChange(selectedType: ItemDropdown) { 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 cd1ed354b..4bd6c7c15 100644 --- a/marketplace-ui/src/app/modules/product/product.service.spec.ts +++ b/marketplace-ui/src/app/modules/product/product.service.spec.ts @@ -201,14 +201,14 @@ describe('ProductService', () => { it('getProductDetailsWithVersion should return a product detail', () => { const productId = 'jira-connector'; - const tag = 'v10.0.10'; + const version = '10.0.10'; - service.getProductDetailsWithVersion(productId, tag).subscribe(data => { + service.getProductDetailsWithVersion(productId, version).subscribe(data => { expect(data).toEqual(MOCK_PRODUCT_DETAIL); }); const req = httpMock.expectOne(request => { - expect(request.url).toEqual(`${API_URI.PRODUCT_DETAILS}/${productId}/${tag}`); + expect(request.url).toEqual(`${API_URI.PRODUCT_DETAILS}/${productId}/${version}`); return true; }); diff --git a/marketplace-ui/src/app/modules/product/product.service.ts b/marketplace-ui/src/app/modules/product/product.service.ts index 35b9d8410..addb2af84 100644 --- a/marketplace-ui/src/app/modules/product/product.service.ts +++ b/marketplace-ui/src/app/modules/product/product.service.ts @@ -41,19 +41,19 @@ export class ProductService { getProductDetailsWithVersion( productId: string, - tag: string + version: string ): Observable { return this.httpClient.get( - `${API_URI.PRODUCT_DETAILS}/${productId}/${tag}` + `${API_URI.PRODUCT_DETAILS}/${productId}/${version}` ); } getBestMatchProductDetailsWithVersion( productId: string, - tag: string + version: string ): Observable { return this.httpClient.get( - `${API_URI.PRODUCT_DETAILS}/${productId}/${tag}/bestmatch` + `${API_URI.PRODUCT_DETAILS}/${productId}/${version}/bestmatch` ); } diff --git a/marketplace-ui/src/app/shared/components/error-page/error-page.component.html b/marketplace-ui/src/app/shared/components/error-page/error-page.component.html index bf6cbc0f2..b4434506f 100644 --- a/marketplace-ui/src/app/shared/components/error-page/error-page.component.html +++ b/marketplace-ui/src/app/shared/components/error-page/error-page.component.html @@ -1,8 +1,8 @@
+ class="d-flex flex-column justify-content-center module-gap col-12 col-lg-4 text-center text-lg-start"> @if (errorId !== undefined) {
{{ 'common.error.code' | translate }}: {{ errorId }} @@ -14,6 +14,7 @@
-
-
+