Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MARP-943 market website detail page of employee onboarding is displayed wrong #127

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9715d30
update service
ntqdinh-axonivy Aug 30, 2024
de3594e
update test case
ntqdinh-axonivy Aug 30, 2024
7ace032
Update ProductServiceImplTest.java
ntqdinh-axonivy Aug 30, 2024
6d767df
Merge branch 'develop' into feature/MARP-943-Market-website-Detail-pa…
ntqdinh-axonivy Aug 30, 2024
9c33ea8
update service
ntqdinh-axonivy Sep 4, 2024
1adf59a
update UI
ntqdinh-axonivy Sep 4, 2024
e7693c8
update UI
ntqdinh-axonivy Sep 5, 2024
96e69a3
handle sonnar
ntqdinh-axonivy Sep 5, 2024
8b56aff
Update product-detail.component.spec.ts
ntqdinh-axonivy Sep 5, 2024
d45bbbd
refactor code & unit test
ntqdinh-axonivy Sep 5, 2024
55f07dc
rename test case
ntqdinh-axonivy Sep 5, 2024
4538597
Update product-detail-information-tab.component.html
ntqdinh-axonivy Sep 5, 2024
caf0050
Merge branch 'bugfix/MARP-1025-Market-website-Wrong-display-in-inform…
ntqdinh-axonivy Sep 6, 2024
7d19cd1
Merge branch 'develop' into feature/MARP-943-Market-website-Detail-pa…
ntqdinh-axonivy Sep 6, 2024
dda280d
Update ProductServiceImpl.java
ntqdinh-axonivy Sep 6, 2024
2cdaf11
update test case
ntqdinh-axonivy Sep 6, 2024
87c21f2
Update product-detail.component.html
ntqdinh-axonivy Sep 6, 2024
2eeab04
update test case
ntqdinh-axonivy Sep 6, 2024
c4599cd
handle feedback
ntqdinh-axonivy Sep 9, 2024
4cf92d7
Merge branch 'develop' into feature/MARP-943-Market-website-Detail-pa…
ntqdinh-axonivy Sep 10, 2024
de9bbf8
Update en.yaml
ntqdinh-axonivy Sep 10, 2024
18e2638
Update product-detail.component.spec.ts
ntqdinh-axonivy Sep 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ public interface GHAxonIvyProductRepoService {
ProductModuleContent getReadmeAndProductContentsFromTag(Product product, GHRepository ghRepository, String tag);

List<MavenArtifact> convertProductJsonToMavenProductInfo(GHContent content) throws IOException;

void extractReadMeFileFromContents(Product product, List<GHContent> contents, ProductModuleContent productModuleContent);
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,19 @@ public ProductModuleContent getReadmeAndProductContentsFromTag(Product product,
productModuleContent.setProductId(product.getId());
productModuleContent.setTag(tag);
updateDependencyContentsFromProductJson(productModuleContent, contents , product);
extractReadMeFileFromContents(product, contents, productModuleContent);
} catch (Exception e) {
log.error("Cannot get product.json content {}", e.getMessage());
return null;
}
return productModuleContent;
}

public void extractReadMeFileFromContents(Product product, List<GHContent> contents, ProductModuleContent productModuleContent) {
try {
List<GHContent> readmeFiles = contents.stream().filter(GHContent::isFile)
.filter(content -> content.getName().startsWith(ReadmeConstants.README_FILE_NAME)).toList();
Map<String,Map<String,String>> moduleContents = new HashMap<>();
Map<String, Map<String, String>> moduleContents = new HashMap<>();
if (!CollectionUtils.isEmpty(readmeFiles)) {
for (GHContent readmeFile : readmeFiles) {
String readmeContents = new String(readmeFile.read().readAllBytes());
Expand All @@ -185,10 +195,8 @@ public ProductModuleContent getReadmeAndProductContentsFromTag(Product product,
productModuleContent.setSetup(replaceEmptyContentsWithEnContent(moduleContents.get(SETUP)));
}
} catch (Exception e) {
log.error("Cannot get product.json and README file's content {}", e.getMessage());
return null;
log.error("Cannot get README file's content {}", e.getMessage());
}
return productModuleContent;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
package com.axonivy.market.service.impl;

import static com.axonivy.market.enums.DocumentField.MARKET_DIRECTORY;
import static com.axonivy.market.enums.DocumentField.SHORT_DESCRIPTIONS;

import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.EMPTY;

import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import com.axonivy.market.util.VersionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.kohsuke.github.GHCommit;
import org.kohsuke.github.GHContent;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHTag;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import com.axonivy.market.constants.CommonConstants;
import com.axonivy.market.constants.GitHubConstants;
import com.axonivy.market.constants.ProductJsonConstants;
Expand All @@ -26,47 +65,10 @@
import com.axonivy.market.repository.ProductModuleContentRepository;
import com.axonivy.market.repository.ProductRepository;
import com.axonivy.market.service.ProductService;
import com.axonivy.market.util.VersionUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.kohsuke.github.GHCommit;
import org.kohsuke.github.GHContent;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHTag;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import static com.axonivy.market.enums.DocumentField.MARKET_DIRECTORY;
import static com.axonivy.market.enums.DocumentField.SHORT_DESCRIPTIONS;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.EMPTY;

@Log4j2
@Service
Expand All @@ -93,6 +95,7 @@ public class ProductServiceImpl implements ProductService {
private String marketRepoBranch;

public static final String NON_NUMERIC_CHAR = "[^0-9.]";
private static final String INITIAL_VERSION = "1.0";
private final SecureRandom random = new SecureRandom();

public ProductServiceImpl(ProductRepository productRepository,
Expand Down Expand Up @@ -343,11 +346,23 @@ private void syncProductsFromGitHubRepo() {
if (StringUtils.isNotBlank(product.getRepositoryName())) {
updateProductCompatibility(product);
getProductContents(product);
} else {
updateProductContentForNonStandardProduct(ghContentEntity, product);
}
productRepository.save(product);
});
}

private void updateProductContentForNonStandardProduct(Map.Entry<String, List<GHContent>> ghContentEntity, Product product) {
ProductModuleContent initialContent = new ProductModuleContent();
initialContent.setTag(INITIAL_VERSION);
initialContent.setProductId(product.getId());
product.setReleasedVersions(List.of(INITIAL_VERSION));
product.setNewestReleaseVersion(INITIAL_VERSION);
axonIvyProductRepoService.extractReadMeFileFromContents(product, ghContentEntity.getValue(), initialContent);
productModuleContentRepository.save(initialContent);
}

private void getProductContents(Product product) {
try {
GHRepository productRepo = gitHubService.getRepository(product.getRepositoryName());
Expand Down Expand Up @@ -404,11 +419,9 @@ private void updateProductCompatibility(Product product) {
if (StringUtils.isNotBlank(product.getCompatibility())) {
return;
}
String oldestTag =
getProductReleaseTags(product).stream().map(tag -> tag.getName().replaceAll(NON_NUMERIC_CHAR, Strings.EMPTY))
.distinct().sorted(Comparator.reverseOrder()).reduce((tag1, tag2) -> tag2).orElse(null);
if (oldestTag != null) {
String compatibility = getCompatibilityFromOldestTag(oldestTag);
String oldestVersion = VersionUtils.getOldestVersion(getProductReleaseTags(product));
if (oldestVersion != null) {
String compatibility = getCompatibilityFromOldestTag(oldestVersion);
product.setCompatibility(compatibility);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import com.axonivy.market.enums.NonStandardProduct;
import org.apache.commons.lang3.BooleanUtils;
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;
Expand All @@ -17,6 +19,8 @@
import java.util.stream.Stream;

public class VersionUtils {
public static final String NON_NUMERIC_CHAR = "[^0-9.]";

private VersionUtils() {
}
public static List<String> getVersionsToDisplay(List<String> versions, Boolean isShowDevVersion, String designerVersion) {
Expand Down Expand Up @@ -112,6 +116,15 @@ public static String convertVersionToTag(String productId, String version) {
return GitHubConstants.STANDARD_TAG_PREFIX.concat(version);
}

public static String getOldestVersion(List<GHTag> tags) {
String result = StringUtils.EMPTY;
if (!CollectionUtils.isEmpty(tags)) {
List<String> releasedTags = tags.stream().map(tag -> tag.getName().replaceAll(NON_NUMERIC_CHAR, Strings.EMPTY))
.distinct().sorted(new LatestVersionComparator()).toList();
return CollectionUtils.lastElement(releasedTags);
}
return result;
}
public static List<String> getReleaseTagsFromProduct(Product product) {
if (Objects.isNull(product)) {
return new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class ProductServiceImplTest extends BaseSetup {
private static final String SHA1_SAMPLE = "35baa89091b2452b77705da227f1a964ecabc6c8";
public static final String RELEASE_TAG = "v10.0.2";
private static final String INSTALLATION_FILE_PATH = "src/test/resources/installationCount.json";
private static final String EMPTY_SOURCE_URL_META_JSON_FILE = "/emptySourceUrlMeta.json";

private String keyword;
private String language;
Expand Down Expand Up @@ -121,7 +122,10 @@ class ProductServiceImplTest extends BaseSetup {
ArgumentCaptor<Product> argumentCaptor = ArgumentCaptor.forClass(Product.class);

@Captor
ArgumentCaptor<ArrayList<ProductModuleContent>> argumentCaptorProductModuleContent;
ArgumentCaptor<ArrayList<ProductModuleContent>> argumentCaptorProductModuleContents;

@Captor
ArgumentCaptor<ProductModuleContent> argumentCaptorProductModuleContent;

@Mock
private GHAxonIvyProductRepoService ghAxonIvyProductRepoService;
Expand Down Expand Up @@ -335,13 +339,33 @@ void testSyncProductsFirstTime() throws IOException {

// Executes
productService.syncLatestDataFromMarketRepo();
verify(productModuleContentRepository).saveAll(argumentCaptorProductModuleContent.capture());
verify(productModuleContentRepository).saveAll(argumentCaptorProductModuleContents.capture());
verify(productRepository).save(argumentCaptor.capture());

assertThat(argumentCaptorProductModuleContent.getValue()).usingRecursiveComparison()
assertThat(argumentCaptorProductModuleContents.getValue()).usingRecursiveComparison()
.isEqualTo(List.of(mockReadmeProductContent()));
}

@Test
void testSyncProductsFirstTimeWithOutSourceUrl() throws IOException {
var mockCommit = mockGHCommitHasSHA1(SHA1_SAMPLE);
when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit);
when(repoMetaRepository.findByRepoName(anyString())).thenReturn(null);

var mockContent = mockGHContentAsMetaJSON();
InputStream inputStream = this.getClass().getResourceAsStream(EMPTY_SOURCE_URL_META_JSON_FILE);
when(mockContent.read()).thenReturn(inputStream);

Map<String, List<GHContent>> mockGHContentMap = new HashMap<>();
mockGHContentMap.put(SAMPLE_PRODUCT_ID, List.of(mockContent));
when(marketRepoService.fetchAllMarketItems()).thenReturn(mockGHContentMap);

// Executes
productService.syncLatestDataFromMarketRepo();
verify(productModuleContentRepository).save(argumentCaptorProductModuleContent.capture());
assertEquals("1.0", argumentCaptorProductModuleContent.getValue().getTag());
}

@Test
void testSyncProductsSecondTime() throws IOException {
var gitHubRepoMeta = mock(GitHubRepoMeta.class);
Expand Down Expand Up @@ -375,7 +399,7 @@ void testSyncProductsSecondTime() throws IOException {
// Executes
productService.syncLatestDataFromMarketRepo();

verify(productModuleContentRepository).saveAll(argumentCaptorProductModuleContent.capture());
verify(productModuleContentRepository).saveAll(argumentCaptorProductModuleContents.capture());
verify(productRepository).save(argumentCaptor.capture());
assertThat(argumentCaptor.getValue().getProductModuleContent()).usingRecursiveComparison()
.isEqualTo(mockReadmeProductContent());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
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.InjectMocks;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@ExtendWith(MockitoExtension.class)
Expand Down Expand Up @@ -148,4 +151,32 @@ void testConvertTagsToVersions() {
Assertions.assertEquals("10.0.2", results.get(1));
}

@Test
void testGetOldestVersionWithEmptyTags() {
List<GHTag> 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<GHTag> tags = Arrays.asList(tag1, tag2);

String oldestTag = VersionUtils.getOldestVersion(tags);

Assertions.assertEquals("1.0", oldestTag); // Assuming the replacement of non-numeric characters works correctly
}
}
35 changes: 35 additions & 0 deletions marketplace-service/src/test/resources/emptySourceUrlMeta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"$schema": "https://json-schema.axonivy.com/market/10.0.2/meta.json",
"id": "employee-onboarding",
"version": "1.0",
"name": "Employee Onboarding",
"names": [
{
"locale": "en",
"value": "Employee Onboarding"
},
{
"locale": "de",
"value": "Mitarbeiter Onboarding"
}
],
"description": "This solution helps HR managers to accelerate time-to-market for employee onboarding.",
"descriptions": [
{
"locale": "en",
"value": "This solution helps HR managers to accelerate time-to-market for employee onboarding."
},
{
"locale": "de",
"value": "HR-Manager können mit dieser Lösung die Time-to-Market für die Einführung neuer Mitarbeiter effektiv reduzieren."
}
],
"type": "solution",
"cost": "paid",
"language": "EN",
"industry": "Cross-Industry",
"tags": [
"hr"
],
"contactUs": true
}
Loading
Loading