diff --git a/marketplace-build/dev/docker-compose.yml b/marketplace-build/dev/docker-compose.yml index ae81c561..fdc6c6b7 100644 --- a/marketplace-build/dev/docker-compose.yml +++ b/marketplace-build/dev/docker-compose.yml @@ -24,7 +24,7 @@ services: volumes: - /home/axonivy/marketplace/data/market-installations.json:/app/data/market-installation.json - marketcache:/app/data/market-cache - - ./logs:/app/logs + - ../logs:/app/logs environment: - MONGODB_HOST=${SERVICE_MONGODB_HOST} - MONGODB_DATABASE=${SERVICE_MONGODB_DATABASE} diff --git a/marketplace-build/docker-compose.yml b/marketplace-build/docker-compose.yml index d57c8ad8..49c421c6 100644 --- a/marketplace-build/docker-compose.yml +++ b/marketplace-build/docker-compose.yml @@ -24,7 +24,7 @@ services: volumes: - /home/axonivy/marketplace/data/market-installations.json:/app/data/market-installation.json - marketcache:/app/data/market-cache - - ./logs:/app/logs + - ../logs:/app/logs environment: - MONGODB_HOST=${SERVICE_MONGODB_HOST} - MONGODB_DATABASE=${SERVICE_MONGODB_DATABASE} diff --git a/marketplace-build/release/docker-compose.yml b/marketplace-build/release/docker-compose.yml index 018614b7..f483fba1 100644 --- a/marketplace-build/release/docker-compose.yml +++ b/marketplace-build/release/docker-compose.yml @@ -22,7 +22,7 @@ services: volumes: - /home/axonivy/marketplace/data/market-installations.json:/app/data/market-installation.json - marketcache:/app/data/market-cache - - ./logs:/app/logs + - ../logs:/app/logs environment: - MONGODB_HOST=${SERVICE_MONGODB_HOST} - MONGODB_DATABASE=${SERVICE_MONGODB_DATABASE} diff --git a/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java index d8cf56e7..6030dae0 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java @@ -42,7 +42,6 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -173,11 +172,13 @@ public List getSecurityDetailsForAllProducts(String accessT GitHub gitHub = getGitHub(accessToken); GHOrganization organization = gitHub.getOrganization(orgName); - return organization.listRepositories().toList().stream() - .map(repo -> CompletableFuture.supplyAsync(() -> fetchSecurityInfoSafe(repo, organization, accessToken), taskScheduler.getScheduledExecutor())) - .map(CompletableFuture::join) - .sorted(Comparator.comparing(ProductSecurityInfo::getRepoName)) - .collect(Collectors.toList()); + List> futures = organization.listRepositories().toList().stream() + .map(repo -> CompletableFuture.supplyAsync(() -> fetchSecurityInfoSafe(repo, organization, accessToken), + taskScheduler.getScheduledExecutor())).toList(); + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> futures.stream().map(CompletableFuture::join).sorted( + Comparator.comparing(ProductSecurityInfo::getRepoName)).collect(Collectors.toList())).join(); } catch (IOException e) { log.error(e.getStackTrace()); return Collections.emptyList(); 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 9a9e0b8c..a90e3be0 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 @@ -22,12 +22,16 @@ import static com.axonivy.market.constants.ProductJsonConstants.DEFAULT_PRODUCT_TYPE; public class ProductContentUtils { - public static final String DEMO_SETUP_TITLE = "(?i)## Demo|## Setup"; + /* + * Accept any combination of #, can be ## or ###, and whitespaces before Demo/Setup word + * Match exactly Demo or Setup + */ + public static final String DEMO_SETUP_TITLE = "(?m)^[#\\s]*##?\\s*(Demo|Setup)\\s*$"; private static final String HASH = "#"; public static final String DESCRIPTION = "description"; public static final String DEMO = "demo"; public static final String SETUP = "setup"; - public static final String README_IMAGE_FORMAT = "\\(([^)]*?/)?%s\\)"; + public static final String README_IMAGE_FORMAT = "\\(([^)]*?/)?%s(\\s+\"[^\"]+\")?\\)"; public static final String IMAGE_DOWNLOAD_URL_FORMAT = "(%s)"; private ProductContentUtils() { @@ -141,6 +145,11 @@ public static void updateProductModuleTabContents(ProductModuleContent productMo productModuleContent.setSetup(replaceEmptyContentsWithEnContent(moduleContents.get(SETUP))); } + /** + * Cover some inconsistent cases: + * Products contain image names in round brackets (employee-onboarding, demo-projects, etc.) + * Image with name contains in other images' (mattermost) + */ public static String replaceImageDirWithImageCustomId(Map imageUrls, String readmeContents) { for (Map.Entry entry : imageUrls.entrySet()) { String imagePattern = String.format(README_IMAGE_FORMAT, Pattern.quote(entry.getKey())); 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 8526630e..27913863 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java +++ b/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java @@ -55,6 +55,7 @@ public class BaseSetup { protected static final String MOCK_PRODUCT_REPOSITORY_NAME = "axonivy-market/bpmn-statistic"; protected static final String MOCK_IMAGE_ID_FORMAT_1 = "imageId-66e2b14868f2f95b2f95549a"; protected static final String MOCK_IMAGE_ID_FORMAT_2 = "imageId-66e2b14868f2f95b2f95550a"; + protected static final String MOCK_IMAGE_ID_FORMAT_3 = "imageId-66e2b14868f2f95b2f95551a"; 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"; diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/GitHubServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/GitHubServiceImplTest.java index 97d210d6..3da6c0ed 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/GitHubServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/GitHubServiceImplTest.java @@ -58,7 +58,7 @@ class GitHubServiceImplTest { private GitHubAccessTokenResponse gitHubAccessTokenResponse; @Mock - private GHTeam team1; + private GHTeam ghTeam; @Spy @InjectMocks @@ -281,7 +281,7 @@ void testIsUserInOrganizationAndTeam_TeamNotFound() throws IOException { String organization = "my-org"; String teamName = "my-team"; Set teams = new HashSet<>(); - teams.add(team1); + teams.add(ghTeam); Map> hashMapTeams = new HashMap<>(); hashMapTeams.put(organization, teams); when(gitHub.getMyTeams()).thenReturn(hashMapTeams); @@ -296,8 +296,8 @@ void testIsUserInOrganizationAndTeam_TeamFound() throws IOException { String organization = "my-org"; String teamName = "my-team"; Set teams = new HashSet<>(); - when(team1.getName()).thenReturn(teamName); - teams.add(team1); + when(ghTeam.getName()).thenReturn(teamName); + teams.add(ghTeam); Map> hashMapTeams = new HashMap<>(); hashMapTeams.put(organization, teams); when(gitHub.getMyTeams()).thenReturn(hashMapTeams); @@ -511,4 +511,17 @@ void testGetDependabotAlerts_Disabled() { Dependabot result = gitHubService.getDependabotAlerts(ghRepository, ghOrganization, accessToken); assertEquals(AccessLevel.DISABLED, result.getStatus()); } + + @Test + void testGetSecurityDetailsForAllProducts() throws Exception { + String accessToken = "mockAccessToken"; + String orgName = "mockOrganization"; + GHOrganization ghOrganization = mock(GHOrganization.class); + when(gitHubService.getGitHub(accessToken)).thenReturn(gitHub); + when(gitHub.getOrganization(orgName)).thenReturn(ghOrganization); + PagedIterable mockPagedIterable = mock(PagedIterable.class); + when(ghOrganization.listRepositories()).thenReturn(mockPagedIterable); + List result = gitHubService.getSecurityDetailsForAllProducts(accessToken, orgName); + assertEquals(0, result.size()); + } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/util/ProductContentUtilsTest.java b/marketplace-service/src/test/java/com/axonivy/market/util/ProductContentUtilsTest.java index c302c66e..b1199ede 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/util/ProductContentUtilsTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/util/ProductContentUtilsTest.java @@ -50,10 +50,11 @@ void testReplaceImageDirWithImageCustomId() { Map imageUrls = new HashMap<>(); imageUrls.put("slash-command.png", MOCK_IMAGE_ID_FORMAT_1); imageUrls.put("create-slash-command.png", MOCK_IMAGE_ID_FORMAT_2); + imageUrls.put("screen2.png", MOCK_IMAGE_ID_FORMAT_3); String expectedResult = readmeContents.replace("images/slash-command.png", MOCK_IMAGE_ID_FORMAT_1).replace("images/create-slash-command.png", - MOCK_IMAGE_ID_FORMAT_2); + MOCK_IMAGE_ID_FORMAT_2).replace("screen2.png \"Restful Person Manager\"", MOCK_IMAGE_ID_FORMAT_3); String updatedContents = ProductContentUtils.replaceImageDirWithImageCustomId(imageUrls, readmeContents); assertEquals(expectedResult, updatedContents); @@ -114,6 +115,40 @@ void testGetExtractedPartsOfEmptyReadme() { assertTrue(StringUtils.isBlank(readmeContentsModel.getSetup())); } + @Test + void testGetExtractedPartsOfReadmeAtCorrectHeadings() { + String readmeContents = getMockReadmeContent(); + ReadmeContentsModel readmeContentsModel = ProductContentUtils.getExtractedPartsOfReadme(readmeContents); + assertTrue(readmeContentsModel.getDescription().startsWith("Axon Ivy’s mattermost connector")); + assertTrue(readmeContentsModel.getDemo().startsWith("### Demo sample")); + assertTrue(readmeContentsModel.getSetup().startsWith("### Setup guideline")); + } + + @Test + void testGetExtractedPartsOfReadmeWithInconsistentFormats() { + String readmeContentsWithHeading3 = """ + #Product-name + Test README + ### Setup + Setup content (./image.png)"""; + ReadmeContentsModel readmeContentsModel = ProductContentUtils.getExtractedPartsOfReadme(readmeContentsWithHeading3); + assertTrue(readmeContentsModel.getDescription().startsWith("Test README")); + assertTrue(readmeContentsModel.getSetup().startsWith("Setup content (./image.png)")); + + String readmeContentsWithSpaceHeading = """ + #Product-name + Test README + ##Demo + ### Demo project + Demo content + ## Setup + Setup content (./image.png)"""; + ReadmeContentsModel readmeContentsModel1 = + ProductContentUtils.getExtractedPartsOfReadme(readmeContentsWithSpaceHeading); + assertTrue(readmeContentsModel1.getDemo().startsWith("### Demo project")); + assertTrue(readmeContentsModel1.getSetup().startsWith("Setup content (./image.png)")); + } + @Test void testHasImageDirectives() { String readmeContents = getMockReadmeContent(); diff --git a/marketplace-service/src/test/resources/README.md b/marketplace-service/src/test/resources/README.md index c86eea4e..d7ef143e 100644 --- a/marketplace-service/src/test/resources/README.md +++ b/marketplace-service/src/test/resources/README.md @@ -9,9 +9,11 @@ This connector: - allow you to start the Axon Ivy process by hitting the slash command key from the mattermost's channel. - allow you to send a message to the mattermost's channel from the Axon Ivy workplace. - notifies users on the channel for new Axon Ivy workflow Tasks. + ![Restful Person Manager](screen2.png "Restful Person Manager") ## Demo +### Demo sample 1. Hit the slash command key on the channel's chat. The Axon Ivy process will be triggered and create a new task. The task's information will be sent to the channel by a message. @@ -20,6 +22,7 @@ This connector: ## Setup +### Setup guideline Mattermost Instance 1. Ref to [Deploy Mattermost](https://docs.mattermost.com/guides/deployment.html).